> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aurous-labs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Mint a character upload URL

> Get a signed PUT URL for one character reference image.

`POST /v1/characters/uploads/init` is the first step of the **upload flow** for creating a character. It returns a short-lived signed PUT URL plus an opaque `upload_id`. Upload your image bytes to that URL, then pass the `upload_id` to [`POST /v1/characters`](/api-reference/characters/create-character) (or call [`POST /v1/characters/uploads/classify`](/api-reference/characters/upload-classify) first to detect the pose).

You typically call this endpoint **once per reference image**. A character supports 1–6 refs.

## When to use

* You already have ref images on disk or in your own storage and want to attach them to a new character without round-tripping bytes through your API.
* You want to capture pose metadata before committing the character (use `upload-classify`).

If instead you want the platform to generate refs from a text description, skip the upload flow and pass `generate: true` + `attributes` to `POST /v1/characters` (the **synthesize flow**).

## Lifecycle

1. `POST /v1/characters/uploads/init` → `{ upload_id, upload_url, expires_at }`.
2. `PUT <upload_url>` with the image bytes (any well-formed PUT, no extra headers required).
3. Optional: `POST /v1/characters/uploads/classify` with the same `upload_id` to detect the pose.
4. `POST /v1/characters` with `{ upload_ids: [upl_..., ...] }` to consume the uploads and create the character.

## Example

<CodeGroup>
  ```bash cURL theme={null}
  # 1. Mint the URL
  curl -X POST https://api.aurous-labs.com/v1/characters/uploads/init \
    -H "X-Api-Key: $AUROUS_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "filename": "front-portrait.jpg",
      "extension": "jpg",
      "content_type": "image/jpeg"
    }'

  # Response: {
  #   "upload_id":      "upl_01H...",
  #   "upload_url":     "https://...",
  #   "upload_headers": { "Content-Type": "image/jpeg" },
  #   "expires_at":     "..."
  # }

  # 2. Upload the bytes (set every header from `upload_headers` on the PUT)
  curl -X PUT "$UPLOAD_URL" \
    -H "Content-Type: image/jpeg" \
    --data-binary @ref-portrait.jpg
  ```

  ```typescript Node.js theme={null}
  const init = await fetch("https://api.aurous-labs.com/v1/characters/uploads/init", {
    method: "POST",
    headers: {
      "X-Api-Key": process.env.AUROUS_API_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      filename: "front-portrait.jpg",
      extension: "jpg",
      content_type: "image/jpeg",
    }),
  }).then((r) => r.json());

  // Echo every header from `upload_headers` on the PUT so the storage
  // layer accepts the upload without rewriting the Content-Type.
  await fetch(init.upload_url, {
    method: "PUT",
    headers: init.upload_headers,
    body: fs.readFileSync("./ref-portrait.jpg"),
  });

  console.log(init.upload_id); // pass to POST /v1/characters
  ```

  ```python Python theme={null}
  import os, requests

  init = requests.post(
      "https://api.aurous-labs.com/v1/characters/uploads/init",
      headers={"X-Api-Key": os.environ["AUROUS_API_KEY"]},
      json={
          "filename": "front-portrait.jpg",
          "extension": "jpg",
          "content_type": "image/jpeg",
      },
  ).json()

  # Echo every header from `upload_headers` on the PUT so the storage
  # layer accepts the upload without rewriting the Content-Type.
  with open("ref-portrait.jpg", "rb") as f:
      requests.put(init["upload_url"], data=f.read(), headers=init["upload_headers"])

  print(init["upload_id"])
  ```
</CodeGroup>

## Limits

* **Rate limit**: bucket `characters_post` — 30 requests/min sustained, 60 burst per team.
* **Upload URL TTL**: 15 minutes. Mint a fresh URL if the PUT lands later.
* **Upload ticket TTL**: 24 hours. After that the bytes are evicted and `upload_id` is no longer accepted on `POST /v1/characters`.

## Common pitfalls

* The `upload_url` is **not** authenticated — the signature is in the query string. Do not add `X-Api-Key` to the PUT.
* One ticket = one image. Mint multiple tickets in parallel for multi-ref characters.
* Browsers may need a CORS-aware proxy in front of the PUT — the V1 surface targets server-side integrators and does not advertise CORS headers on the upload host.


## OpenAPI

````yaml POST /v1/characters/uploads/init
openapi: 3.0.0
info:
  title: Aurous Labs API
  description: >-
    Generate AI images with custom LoRA styles.


    ## Authentication

    All requests require an API key passed in the `X-Api-Key` header.

    Create API keys in your
    [dashboard](https://app.aurous-labs.com/dashboard/api-keys).


    ## Closed-beta access gate

    API keys are scoped to a user. If that user's account is not approved for
    the closed beta, every request returns `403` with one of these `error.code`
    values:


    - `account_pending` — awaiting review

    - `account_rejected` — declined post-signup

    - `account_suspended` — was approved, then suspended


    There is no retry — contact support to be approved. The same codes are
    emitted by the WebSocket gateway via 4001 close.


    ## Common headers

    Every response carries `Aurous-Request-Id` (a server-minted `req_<ULID>` for
    support tracing) and `Aurous-Version` (the API version applied to the
    response). Optionally pin a version on the request with `Aurous-Version:
    YYYY-MM-DD` — defaults to your team's pinned version.


    ## Quick Start

    ```bash

    curl -X POST https://api.aurous-labs.com/v1/images \
      -H "X-Api-Key: al_live_your_key" \
      -H "Content-Type: application/json" \
      -d '{"prompt": "A golden sunset over mountains", "lora_id": "your-lora-id", "size": "2k_1_1"}'
    ```
  version: 1.0.0
  contact: {}
servers:
  - url: https://api.aurous-labs.com
    description: Production
  - url: https://api.preprod.aurous-labs.com
    description: Preprod (staging)
security: []
tags:
  - name: Seedance (raw)
    description: >-
      Drop-in raw passthrough for Seedance video generation. Point the official
      Seedance provider SDK at this API's base URL and authenticate with your
      Aurous API key in the `X-Api-Key` header — request bodies are forwarded to
      the provider verbatim and responses come back shape-identical, so you keep
      the provider's exact request/response shapes. Task ids are Aurous-native
      `vid_…` ids. Billing rides response headers, not the body:
      `Aurous-Credits-Held` on the create response and `Aurous-Credits-Charged`
      on a settled, succeeded task read — the body itself stays provider-shaped.
paths:
  /v1/characters/uploads/init:
    post:
      tags:
        - Public API (v1) — Characters
      summary: Mint a signed upload URL for one character reference image
      description: >-
        Returns an `upload_id` and a pre-signed PUT URL valid for 15 minutes.
        Upload bytes via PUT to that URL with the supplied headers. The ticket
        lives 24 hours; on POST /v1/characters with `upload_ids: [upl_*, ...]`
        the bytes are moved to character storage and the upload entry is
        consumed. Mismatched `extension` + `content_type` returns 400
        `invalid_request`.
      operationId: V1CharactersController_initUpload
      parameters:
        - name: Aurous-Version
          in: header
          required: false
          description: >-
            Optional API version pin (YYYY-MM-DD). Defaults to your team's
            pinned version, or the system default `2026-07-16` for
            unauthenticated requests.
          schema:
            type: string
            example: '2026-07-16'
            pattern: ^\d{4}-\d{2}-\d{2}$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CharacterUploadInitDto'
      responses:
        '201':
          description: Upload ticket minted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CharacterUploadInitResponse'
          headers:
            Aurous-Request-Id:
              description: Server-minted request id. Quote this in support tickets.
              schema:
                type: string
                example: req_01HXMQ7Z3K8Y2NABCDEFGHJKMP
            Aurous-Version:
              description: API version pin applied to this response (YYYY-MM-DD).
              schema:
                type: string
                example: '2026-07-16'
            X-RateLimit-Limit:
              description: Bucket capacity (max tokens) for this endpoint class.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Tokens remaining after this request.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Epoch seconds when the bucket would be full again, assuming no
                further requests.
              schema:
                type: integer
                example: 1700000060
        '400':
          description: Validation failed (mismatched extension / content_type)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          headers:
            Aurous-Request-Id:
              description: Server-minted request id. Quote this in support tickets.
              schema:
                type: string
                example: req_01HXMQ7Z3K8Y2NABCDEFGHJKMP
            Aurous-Version:
              description: API version pin applied to this response (YYYY-MM-DD).
              schema:
                type: string
                example: '2026-07-16'
            X-RateLimit-Limit:
              description: Bucket capacity (max tokens) for this endpoint class.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Tokens remaining after this request.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Epoch seconds when the bucket would be full again, assuming no
                further requests.
              schema:
                type: integer
                example: 1700000060
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          headers:
            Aurous-Request-Id:
              description: Server-minted request id. Quote this in support tickets.
              schema:
                type: string
                example: req_01HXMQ7Z3K8Y2NABCDEFGHJKMP
            Aurous-Version:
              description: API version pin applied to this response (YYYY-MM-DD).
              schema:
                type: string
                example: '2026-07-16'
            X-RateLimit-Limit:
              description: Bucket capacity (max tokens) for this endpoint class.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Tokens remaining after this request.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Epoch seconds when the bucket would be full again, assuming no
                further requests.
              schema:
                type: integer
                example: 1700000060
        '403':
          description: >-
            Account not approved for closed beta. error.code is one of
            `account_pending`, `account_rejected`, `account_suspended`. There is
            no retry — contact support to be approved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          headers:
            Aurous-Request-Id:
              description: Server-minted request id. Quote this in support tickets.
              schema:
                type: string
                example: req_01HXMQ7Z3K8Y2NABCDEFGHJKMP
            Aurous-Version:
              description: API version pin applied to this response (YYYY-MM-DD).
              schema:
                type: string
                example: '2026-07-16'
            X-RateLimit-Limit:
              description: Bucket capacity (max tokens) for this endpoint class.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Tokens remaining after this request.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Epoch seconds when the bucket would be full again, assuming no
                further requests.
              schema:
                type: integer
                example: 1700000060
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          headers:
            Aurous-Request-Id:
              description: Server-minted request id. Quote this in support tickets.
              schema:
                type: string
                example: req_01HXMQ7Z3K8Y2NABCDEFGHJKMP
            Aurous-Version:
              description: API version pin applied to this response (YYYY-MM-DD).
              schema:
                type: string
                example: '2026-07-16'
            X-RateLimit-Limit:
              description: Bucket capacity (max tokens) for this endpoint class.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Tokens remaining after this request.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Epoch seconds when the bucket would be full again, assuming no
                further requests.
              schema:
                type: integer
                example: 1700000060
            Retry-After:
              description: >-
                Seconds to wait before retrying. Present on 429 (rate limit) and
                on 503 provider_unavailable. Prefer this over computing
                X-RateLimit-Reset − now.
              schema:
                type: integer
                example: 12
        '503':
          description: >-
            Storage temporarily unavailable — the signed-URL mint failed
            transiently. `error.code` is `provider_unavailable`; retry after the
            seconds in `Retry-After`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          headers:
            Aurous-Request-Id:
              description: Server-minted request id. Quote this in support tickets.
              schema:
                type: string
                example: req_01HXMQ7Z3K8Y2NABCDEFGHJKMP
            Aurous-Version:
              description: API version pin applied to this response (YYYY-MM-DD).
              schema:
                type: string
                example: '2026-07-16'
            X-RateLimit-Limit:
              description: Bucket capacity (max tokens) for this endpoint class.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Tokens remaining after this request.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Epoch seconds when the bucket would be full again, assuming no
                further requests.
              schema:
                type: integer
                example: 1700000060
            Retry-After:
              description: >-
                Seconds to wait before retrying. Present on 429 (rate limit) and
                on 503 provider_unavailable. Prefer this over computing
                X-RateLimit-Reset − now.
              schema:
                type: integer
                example: 12
      security:
        - api-key: []
components:
  schemas:
    CharacterUploadInitDto:
      type: object
      properties:
        filename:
          type: string
          description: >-
            Original filename (used for Content-Disposition; not stored as the
            storage key).
          example: front-portrait.jpg
          minLength: 1
          maxLength: 120
        extension:
          type: string
          description: >-
            File extension. Must match `content_type`; mismatched combinations
            return 400.
          enum:
            - jpeg
            - jpg
            - png
            - webp
          example: jpg
        content_type:
          type: string
          description: >-
            MIME type. Must match `extension`; mismatched combinations return
            400.
          enum:
            - image/jpeg
            - image/png
            - image/webp
          example: image/jpeg
      required:
        - filename
        - extension
        - content_type
    CharacterUploadInitResponse:
      type: object
      properties:
        upload_id:
          type: string
          description: >-
            Upload ticket ID. Use as `upload_id` on POST
            /v1/characters/uploads/classify and POST /v1/characters.
          example: upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR
        upload_url:
          type: string
          description: >-
            Pre-signed PUT URL — upload bytes via HTTP PUT with the headers from
            `upload_headers`. The URL expires at `expires_at`.
          example: https://aurous.storage/.../upload?signature=…
        upload_headers:
          type: object
          description: >-
            HTTP headers to include on the PUT request (Content-Type matches the
            request payload).
          example:
            Content-Type: image/jpeg
          additionalProperties:
            type: string
        expires_at:
          type: string
          description: >-
            Upload URL expiration (ISO 8601). Re-mint via /uploads/init if you
            need to retry past this point.
          example: '2026-05-08T10:15:00Z'
      required:
        - upload_id
        - upload_url
        - upload_headers
        - expires_at
    ErrorResponse:
      type: object
      properties:
        error:
          description: Error payload
          allOf:
            - $ref: '#/components/schemas/ErrorPayload'
      required:
        - error
    ErrorPayload:
      type: object
      properties:
        type:
          type: string
          description: Broad error category
          example: invalid_request
          enum:
            - invalid_request
            - authentication
            - not_found
            - rate_limit
            - server_error
        code:
          type: string
          description: >-
            Stable error code (programmatic discriminator). Closed-beta gate
            emits one of `account_pending`, `account_rejected`,
            `account_suspended` on 403.
          example: balance_too_low
          enum:
            - invalid_request
            - missing_field
            - invalid_format
            - value_out_of_range
            - unsupported_lora_for_mode
            - generation_not_cancellable
            - prompt_blocked
            - reference_blocked
            - output_moderation_rejected
            - unknown_version
            - mutually_exclusive_input
            - character_not_ready
            - parameter_invalid_combination
            - style_retired
            - parameter_invalid
            - too_many_reference_images
            - action_not_available
            - upload_invalid
            - balance_too_low
            - idempotency_key_in_use
            - api_key_not_found
            - payload_too_large
            - missing_api_key
            - invalid_api_key
            - revoked_api_key
            - resource_not_found
            - forbidden_resource
            - account_pending
            - account_rejected
            - account_suspended
            - already_approved
            - already_rejected
            - already_suspended
            - invalid_reinstate_target
            - invalid_suspend_target
            - cannot_moderate_admin
            - too_many_requests
            - concurrency_limit_exceeded
            - tpm_rate_limit_exceeded
            - internal_error
            - provider_unavailable
            - provider_timeout
            - provider_not_configured
            - invalid_time_range
            - invalid_bucket_width
            - too_many_buckets
            - too_many_group_by
            - invalid_filter
            - invalid_page_token
            - export_too_large
            - user_already_exists
            - self_invite_forbidden
            - invite_link_failed
            - invite_rate_limited
            - model_not_found
            - model_disabled
            - model_wrong_kind
            - max_tokens_exceeds_hard_cap
            - chat_model_misconfigured
            - embeddings_input_too_large
            - embeddings_unsupported_dimensions
            - pricing_frozen
            - provider_rate_limited
            - chat_provider_request_invalid
            - chat_provider_auth_failed
            - chat_provider_unavailable
            - max_input_tokens_exceeded
            - chat_provider_unknown_error
            - embeddings_provider_unknown_error
            - embeddings_batch_not_supported
            - embeddings_input_too_many_items
            - embeddings_video_unsupported
            - encoding_format_unsupported
            - missing_max_tokens_no_model_default
            - chat_cancel_target_not_found
            - chat_completion_not_found
            - chat_cancel_target_already_terminal
            - chat_cancel_target_not_cancellable
            - tool_choice_required_unsupported
            - response_format_too_large
            - response_format_too_deep
            - invalid_cursor
            - invalid_cursor_for_endpoint
            - model_slug_exists
            - output_expired
            - output_not_available
            - content_filtered
            - image_generation_failed
            - reference_media_invalid
            - reference_media_cap_reached
            - reference_fetch_failed
            - unsupported_auth_method
            - insufficient_scope
            - uploads_expired
            - provider_unknown_error
            - first_frame_too_small
        message:
          type: string
          description: Human-readable message
          example: Team available balance is 1.5 credits, generation requires 2.0.
        param:
          type: object
          description: Field name when the error is parameter-scoped
          example: prompt
          nullable: true
        doc_url:
          type: string
          description: Documentation link for this error code
          example: https://docs.aurous-labs.com/errors#balance_too_low
        request_id:
          type: string
          description: Echoes Aurous-Request-Id — quote in support tickets
          example: req_01HXMQ7Z3K8Y2VNABCDEFGHJKM
      required:
        - type
        - code
        - message
        - doc_url
        - request_id
  securitySchemes:
    api-key:
      type: apiKey
      in: header
      name: X-Api-Key
      description: Your team API key (starts with `al_live_`).

````