> ## 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.

# Create a character

> Upload existing ref images or synthesize 4 ref poses from a description.

`POST /v1/characters` creates a character — a reusable identity asset you can attach to an image or video generation via `character_id` (see [Create an image](/api-reference/images/create-image) or [Create a video](/api-reference/videos/create-video)). There are two **mutually exclusive** flows:

* **Upload flow**: pass `upload_ids` (1–6) collected from [`POST /v1/characters/uploads/init`](/api-reference/characters/upload-init). The platform moves the bytes to character storage and the response comes back with `status: ready` — immediately usable.
* **Synthesize flow**: pass `generate: true` plus an `attributes` object describing who the character is. The platform dispatches a multi-image generation task that produces 4 ref poses (`portrait`, `front`, `side`, `back`). The response returns `status: synthesizing` while generation is in flight, or `status: reviewing` if synthesis completed before the response returned (typical for fast runs). Either way, poll [`GET /v1/characters/{id}`](/api-reference/characters/retrieve-character) until `status: reviewing`, then call [`POST /v1/characters/{id}/save`](/api-reference/characters/save-character) to mark it `ready`.

<Note>
  Send `upload_ids` **or** `generate: true` — never both, never neither.
  Sending both or neither returns `400 parameter_invalid_combination`.
</Note>

## Upload flow

Use this when you already have ref images. Mint one `upload_id` per file via `POST /v1/characters/uploads/init`, PUT the bytes, then create the character.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.aurous-labs.com/v1/characters \
    -H "X-Api-Key: $AUROUS_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $(uuidgen)" \
    -d '{
      "name": "Aurora",
      "upload_ids": [
        "upl_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
        "upl_01HXMQ87RKZQA0YBYV1V47TPS6"
      ]
    }'
  ```

  ```typescript Node.js theme={null}
  const character = await fetch("https://api.aurous-labs.com/v1/characters", {
    method: "POST",
    headers: {
      "X-Api-Key": process.env.AUROUS_API_KEY!,
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: JSON.stringify({
      name: "Aurora",
      upload_ids: ["upl_01HXMQ7Z3K8Y2VNABCDEFGHJKM", "upl_01HXMQ87RKZQA0YBYV1V47TPS6"],
    }),
  }).then((r) => r.json());

  console.log(character.id, character.status); // char_..., "ready"
  ```

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

  r = requests.post(
      "https://api.aurous-labs.com/v1/characters",
      headers={
          "X-Api-Key": os.environ["AUROUS_API_KEY"],
          "Idempotency-Key": str(uuid.uuid4()),
      },
      json={
          "name": "Aurora",
          "upload_ids": [
              "upl_01HXMQ7Z3K8Y2VNABCDEFGHJKM",
              "upl_01HXMQ87RKZQA0YBYV1V47TPS6",
          ],
      },
  ).json()

  print(r["id"], r["status"])  # char_..., "ready"
  ```
</CodeGroup>

## Synthesize flow

Use this when you want the platform to generate the refs from a description. The `attributes` object is locked at v1.0 to 7 typed fields plus a free-text `additional_details` catch-all; on this synthesize flow every field drives generation (on the upload flow only `additional_details` is used, and only as a best-effort hint — see [Upload flow](#upload-flow)). Synthesize burns credits at create time (4 generation dispatches), so prefer to estimate cost via `POST /v1/images/estimate` on the equivalent prompt if you need a budget guardrail in your UI.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.aurous-labs.com/v1/characters \
    -H "X-Api-Key: $AUROUS_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $(uuidgen)" \
    -d '{
      "name": "Aurora",
      "generate": true,
      "attributes": {
        "gender": "female",
        "age": 28,
        "ethnicity": "northern european",
        "hair_color": "auburn",
        "hair_style": "long waves",
        "eye_color": "green",
        "body_type": "athletic",
        "additional_details": "scar across left cheekbone, freckles"
      }
    }'
  ```

  ```typescript Node.js theme={null}
  const character = await fetch("https://api.aurous-labs.com/v1/characters", {
    method: "POST",
    headers: {
      "X-Api-Key": process.env.AUROUS_API_KEY!,
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: JSON.stringify({
      name: "Aurora",
      generate: true,
      attributes: {
        gender: "female",
        age: 28,
        ethnicity: "northern european",
        hair_color: "auburn",
        hair_style: "long waves",
        eye_color: "green",
        body_type: "athletic",
        additional_details: "scar across left cheekbone, freckles",
      },
    }),
  }).then((r) => r.json());

  console.log(character.id, character.status); // char_..., "synthesizing" (or "reviewing" if synth was already done)
  ```

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

  r = requests.post(
      "https://api.aurous-labs.com/v1/characters",
      headers={
          "X-Api-Key": os.environ["AUROUS_API_KEY"],
          "Idempotency-Key": str(uuid.uuid4()),
      },
      json={
          "name": "Aurora",
          "generate": True,
          "attributes": {
              "gender": "female",
              "age": 28,
              "ethnicity": "northern european",
              "hair_color": "auburn",
              "hair_style": "long waves",
              "eye_color": "green",
              "body_type": "athletic",
              "additional_details": "scar across left cheekbone, freckles",
          },
      },
  ).json()

  print(r["id"], r["status"])  # char_..., "synthesizing" (or "reviewing" if synth was already done)
  ```
</CodeGroup>

## Status transitions

| Flow                | Initial                                                                    | After processing | Customer action                                                                                                                                                                                                                       |
| ------------------- | -------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Upload              | `ready`                                                                    | —                | Use immediately on `POST /v1/images` or `POST /v1/videos`.                                                                                                                                                                            |
| Synthesize          | `synthesizing` (or `reviewing` if synth completed before the 201 returned) | `reviewing`      | Poll `GET /v1/characters/{id}`. When `reviewing`, call `POST /v1/characters/{id}/save` to publish; or call `POST /v1/characters/{id}/resynthesize` to retry.                                                                          |
| Synthesize (failed) | `synthesizing`                                                             | `failed`         | `error_message` populated. Call `POST /v1/characters/{id}/resynthesize` to retry (reuses the stored `attributes`); if it returns `422 uploads_expired`, delete via `DELETE /v1/characters/{id}` and recreate with fresh `upload_ids`. |

## Limits

* **Rate limit**: bucket `characters_synthesize` — 15 requests/min sustained, 30 burst per team. Both flows ride this bucket since the synthesize discriminator is decided server-side after the request lands.
* **Idempotency**: pass `Idempotency-Key` (any opaque value, 1–256 chars). Same key + same body within 24h replays the cached response with `Aurous-Idempotent-Replayed: true`. Same key + different body returns `409 idempotency_key_in_use`. See [Idempotency](/idempotency) for details.

<Note>
  Always send `Idempotency-Key` on synthesize-flow create — it dispatches
  4 paid generations, so a network retry without a key can double-charge.
</Note>

## Errors

| Code                            | HTTP | When                                                              |
| ------------------------------- | ---- | ----------------------------------------------------------------- |
| `parameter_invalid_combination` | 400  | Sent both `upload_ids` and `generate: true`, or neither.          |
| `upload_invalid`                | 400  | One of the `upload_ids` is unknown, expired, or already consumed. |
| `balance_too_low`               | 402  | Synthesize-flow dispatch when team credits \< cost.               |
| `idempotency_key_in_use`        | 409  | Same `Idempotency-Key` was used with a different body.            |
| `too_many_requests`             | 429  | Burst > 30 or sustained > 15/min.                                 |

## Common pitfalls

* The synthesize flow returns 201 immediately, but the character is **not** usable until it transitions to `ready` — either automatically (upload flow) or via `POST /v1/characters/{id}/save` (synthesize flow). Calling `POST /v1/images` or `POST /v1/videos` with a `synthesizing` or `reviewing` `character_id` returns `400 character_not_ready`.
* An upload flow with a single ref still works. On the upload flow your reference images define identity, so the 7 typed `attributes` (gender, age, etc.) are stored and echoed back but do **not** shape the generated refs — only `additional_details` is applied there, and only as a best-effort hint (your reference images are the primary signal). The synthesize flow (`generate: true`) is the opposite: all `attributes` drive generation, so don't send empty `attributes` there.
* The `attributes` schema is locked for v1.0; new attributes go into `additional_details` until a v1.1 bump introduces them as typed fields.


## OpenAPI

````yaml POST /v1/characters
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:
    post:
      tags:
        - Public API (v1) — Characters
      summary: Create a character (upload OR synthesize flow) — async
      description: >-
        Creates a character and returns immediately with `status:
        "synthesizing"` and `refs: []`. Synthesis (4 reference poses) runs in
        the background — the response is a point-in-time receipt, NOT the
        finished resource.


        **On success** the character becomes `status: "ready"` and a
        `character.completed` webhook fires carrying the full character resource
        (including signed ref URLs and your `client_reference_id`). **On
        failure** the character becomes `status: "failed"`, a `character.failed`
        webhook fires with an `error` object, and your up-front credit charge is
        released — a failed create nets zero charge. **On DELETE while
        synthesizing** the pending job is cancelled, a `character.cancelled`
        webhook fires, and the charge is refunded.


        Completion is delivered to your **account-level webhook subscriptions**
        — register a webhook endpoint subscribed to `character.completed` /
        `character.failed` / `character.cancelled` before relying on this flow
        (there is no per-request `webhook_url`). If you do not use webhooks,
        poll `GET /v1/characters/{id}` until `status: "ready"`. The webhook
        event `data` payloads are documented as the
        `CharacterCompletedEventData` (the full character resource plus
        `operation`), `CharacterFailedEventData` (`status: "failed"` + a
        closed-set `error` object + `usable: false`), and
        `CharacterCancelledEventData` (`status: "cancelled"`) schemas in this
        reference.


        **Upload flow** — supply `upload_ids` (1-6) from POST
        /v1/characters/uploads/init. **Synthesize flow** — supply `generate:
        true` + `attributes`. Exactly one: sending neither, or both, returns 400
        `parameter_invalid_combination`.


        **Idempotent replay note:** the returned `status` is a point-in-time
        receipt. An idempotent replay (`Aurous-Idempotent-Replayed: true`) may
        return a STALE status (e.g. `synthesizing` after the character already
        reached `ready`). The source of truth is `GET /v1/characters/{id}` or
        the webhook — not the cached create body. Likewise `refs` is `[]` on
        this response until the character reaches `ready`.


        **Retry contract:** a failed create is terminal. To retry, re-POST to
        create a fresh character; reuse the same `client_reference_id` to
        correlate the attempts.


        **Rate limit:** 15 requests/minute sustained (burst 30) per team —
        **shared** across `POST /v1/characters`, `POST
        /v1/characters/{id}/refs/regenerate`, and `POST
        /v1/characters/{id}/resynthesize`. A burst of regenerations or
        resynthesizes draws down the same budget as new creates; they are not
        independently budgeted. The live `X-RateLimit-Limit` /
        `X-RateLimit-Remaining` headers are the authoritative current values
        (limits may change — read them from the response, do not hard-code).
      operationId: V1CharactersController_create
      parameters:
        - name: Idempotency-Key
          in: header
          description: >-
            Stripe-style idempotency key (1-256 chars). Same key + same
            canonical-JSON body returns the cached response with
            `Aurous-Idempotent-Replayed: true`. Same key against a different
            route (e.g. previously used on /v1/images) returns `409
            invalid_request / idempotency_key_in_use`. Replay window is 24
            hours. Absent header is treated as non-idempotent (each call
            processes anew).
          required: false
          schema:
            type: string
        - 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/V1CreateCharacterDto'
      responses:
        '201':
          description: >-
            Character accepted; synthesis dispatched (status: synthesizing,
            refs: []).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CharacterResponse'
          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
            Aurous-Idempotent-Replayed:
              description: >-
                Present (literal `true`) when this response was served from a
                stored idempotent replay — the same `Idempotency-Key` +
                canonical body was seen within the 24h window and the original
                response is returned WITHOUT re-executing (no second charge, no
                second task). Absent on the first (fresh) execution and on any
                request sent without an `Idempotency-Key`. Only on the
                idempotency-aware create routes.
              schema:
                type: string
                enum:
                  - 'true'
                example: 'true'
        '400':
          description: Validation failed (missing or conflicting flow inputs)
          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
        '402':
          description: >-
            Insufficient credits — the up-front charge failed
            (`balance_too_low`); nothing is dispatched.
          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
        '404':
          description: Upload not found / cross-team
          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
        '409':
          description: >-
            Idempotency-Key was reused with a different request body, or was
            previously used on a different route
          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: >-
            Character generation is temporarily unavailable, or the platform
            could not verify your account/billing state or prepare the request
            (for example, reference images) before dispatch — error.code is
            `provider_unavailable` in every case. Retry after the number of
            seconds in the `Retry-After` header.
          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:
    V1CreateCharacterDto:
      type: object
      properties:
        name:
          type: string
          description: Display name for the character (1-80 chars).
          example: Aurora the Adventurer
          minLength: 1
          maxLength: 80
        generate:
          type: boolean
          description: >-
            Selects the synthesize flow when true (the platform generates 4 ref
            poses from `attributes`). When omitted/false, `upload_ids` must be
            provided to attach customer-supplied refs. Mutually exclusive with
            `upload_ids`.
          example: false
        upload_ids:
          description: >-
            Upload tickets from POST /v1/characters/uploads/init (1-6). When
            set, the upload flow is used and the server moves bytes from upload
            storage to character storage on create (uploads are consumed).
            Mutually exclusive with `generate: true`.
          example:
            - upl_01HXMQ7Z3K8Y2NABCDEFGHJKMR
          minItems: 1
          maxItems: 6
          type: array
          items:
            type: string
        attributes:
          description: >-
            Character attributes. Required when `generate: true` (synthesize
            flow), where ALL fields drive generation. Optional on the upload
            flow, where the 7 typed fields (gender, age, ethnicity, hair_color,
            hair_style, eye_color, body_type) are stored and echoed back but do
            NOT shape the generated refs — your uploaded images define identity.
            `additional_details` is the exception: it is also applied on the
            upload flow as a free-text styling hint, but treat it as best-effort
            — your uploaded reference images are the primary signal, so supply a
            reference image for any trait you need to guarantee.
          allOf:
            - $ref: '#/components/schemas/V1CharacterAttributesDto'
        client_reference_id:
          type: string
          description: >-
            Optional caller-supplied reference, echoed back on GET
            /v1/characters/{id} and in every character webhook for this
            character. Use it to correlate the webhook with the originating
            record in your system (Stripe-style). Immutable after create. Max
            256 chars.
          maxLength: 256
          example: bot_8472
      required:
        - name
    CharacterResponse:
      type: object
      properties:
        id:
          type: string
          description: Opaque character ID.
          example: char_01HXMQ7Z3K8Y2NABCDEFGHJKMR
        object:
          type: string
          description: Discriminator
          example: character
          enum:
            - character
        name:
          type: string
          description: Display name.
          example: Aurora the Adventurer
        client_reference_id:
          type: object
          description: >-
            Caller-supplied reference echoed back (the value sent on create).
            Null if unset.
          nullable: true
          example: bot_8472
        status:
          type: string
          description: >-
            Lifecycle state. `synthesizing`: synthesize flow running.
            `reviewing`: synthesize completed, awaiting POST /:id/save. `ready`:
            usable on POST /v1/images. `failed`: synthesize failed; use POST
            /:id/resynthesize to retry. `deleted`: soft-deleted (filtered out of
            list endpoint).
          example: ready
          enum:
            - synthesizing
            - reviewing
            - ready
            - failed
            - deleted
        attributes:
          description: Character attributes. Null when unset.
          nullable: true
          allOf:
            - $ref: '#/components/schemas/CharacterAttributesResponse'
        refs:
          description: Reference images (typically 4 poses).
          type: array
          items:
            $ref: '#/components/schemas/CharacterRefEntry'
        error_message:
          type: object
          description: Error message when status is `failed`. Null otherwise.
          nullable: true
          example: null
        aurous_version:
          type: string
          description: >-
            API contract version applied at the time this row was minted (D25 —
            frozen for replay across future version bumps).
          example: '2026-07-16'
        created_at:
          type: string
          description: Creation timestamp (ISO 8601).
          example: '2026-05-08T10:00:00Z'
        updated_at:
          type: string
          description: Last-update timestamp (ISO 8601).
          example: '2026-05-08T10:00:00Z'
      required:
        - id
        - object
        - name
        - status
        - refs
        - created_at
        - updated_at
    ErrorResponse:
      type: object
      properties:
        error:
          description: Error payload
          allOf:
            - $ref: '#/components/schemas/ErrorPayload'
      required:
        - error
    V1CharacterAttributesDto:
      type: object
      properties:
        gender:
          type: string
          maxLength: 40
          example: female
        age:
          type: number
          minimum: 18
          maximum: 120
          example: 28
        ethnicity:
          type: string
          maxLength: 40
          example: east-asian
        hair_color:
          type: string
          maxLength: 40
          example: black
        hair_style:
          type: string
          maxLength: 40
          example: shoulder-length straight
        eye_color:
          type: string
          maxLength: 40
          example: brown
        body_type:
          type: string
          maxLength: 40
          example: athletic
        additional_details:
          type: string
          maxLength: 500
          description: >-
            Free-text catch-all for traits that do not fit the typed fields
            above. Unlike the typed fields, this applies on BOTH flows: on
            synthesize (`generate: true`) it is part of the full attribute
            description; on the upload flow it is the only attribute applied to
            generation (the typed fields are metadata-only there). On the upload
            flow treat it as a best-effort hint — your uploaded reference images
            are the primary signal, so supply a reference image for any trait
            that must be guaranteed. New typed fields land in v1.1 schema bumps.
          example: small scar above right eyebrow; warm smile
    CharacterAttributesResponse:
      type: object
      properties:
        gender:
          type: object
          nullable: true
          example: female
        age:
          type: object
          nullable: true
          example: 28
        ethnicity:
          type: object
          nullable: true
          example: east-asian
        hair_color:
          type: object
          nullable: true
          example: black
        hair_style:
          type: object
          nullable: true
          example: shoulder-length straight
        eye_color:
          type: object
          nullable: true
          example: brown
        body_type:
          type: object
          nullable: true
          example: athletic
        additional_details:
          type: object
          nullable: true
          description: Free-text catch-all for attributes outside the typed fields.
          example: small scar above right eyebrow; warm smile
    CharacterRefEntry:
      type: object
      properties:
        pose:
          type: string
          description: Pose label for this reference image.
          enum:
            - portrait
            - front
            - side
            - back
            - other
          example: front
        url:
          type: string
          description: >-
            Signed URL for the ref image. TTL is 24h — re-fetch the character
            (GET /v1/characters/{id}) to mint a fresh URL set when one expires.
          example: https://api.aurous-labs.com/storage/.../front.bin?token=…
      required:
        - pose
        - url
    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_`).

````