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

> Create an empty character in `draft` — free, renders nothing, and is the starting point of the builder.

`POST /v1/characters/drafts` creates a character in `draft`: **no credits are charged and nothing is rendered**. A draft is the empty shell the builder fills — file your own photos into its views with [`PUT /v1/characters/{id}/refs/{view}`](/api-reference/characters/put-ref), then call [`POST /v1/characters/{id}/build`](/api-reference/characters/build-character) to render whatever is still missing.

A draft is **not** usable for generation: `POST /v1/images` or `POST /v1/videos` with its `character_id` returns `400 character_not_ready`.

Drafts appear in [`GET /v1/characters`](/api-reference/characters/list-characters) alongside everything else; filter with `?status=draft`.

<Note>
  Compare with [`POST /v1/characters`](/api-reference/characters/create-character), the one-shot create: it charges for all eight views up front and renders them immediately. Use a draft when you want to supply some of the photos yourself and pay only for the views the platform actually renders.
</Note>

## When to use

* You have your own photos for some views and want the platform to render only the rest.
* You are building an upload UI and want a real character id to attach photos to before anything is charged.
* You want to stage a character now and decide later whether to build it — an unbuilt draft costs nothing.

## Examples

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

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

  console.log(draft.status);               // "draft"
  console.log(draft.views.length);         // 8 — every slot "missing"
  console.log(draft.build.missing_views);  // all eight view names
  ```

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

  draft = requests.post(
      "https://api.aurous-labs.com/v1/characters/drafts",
      headers={
          "X-Api-Key": os.environ["AUROUS_API_KEY"],
          "Idempotency-Key": str(uuid.uuid4()),
      },
      json={"name": "Aurora", "nudity": "clothed", "client_reference_id": "bot_8472"},
  ).json()

  print(draft["status"], len(draft["views"]))  # draft 8
  ```
</CodeGroup>

## Body

| Field                 | Required | Description                                                                                                                                                                                                                                                                                                                                                                |
| --------------------- | -------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                |      yes | Display name, 1–80 characters.                                                                                                                                                                                                                                                                                                                                             |
| `nudity`              |       no | `nude` or `clothed`. Omit to take your team's default. Changeable with `PATCH` **while the character is still a `draft`**; fixed once it leaves `draft`. On teams that always render clothed, an explicit `nude` returns `400 nudity_not_allowed`.                                                                                                                         |
| `attributes`          |       no | The same attribute object [`POST /v1/characters`](/api-reference/characters/create-character) takes. Optional here — but a build needs identity, and `attributes` supply it **only for a draft with no uploaded photos at all**. If you will upload any photo, one of them must show the face instead. See [Build a character](/api-reference/characters/build-character). |
| `client_reference_id` |       no | Your own id, echoed on every read and every `character.*` webhook for this character. Immutable after create. Max 256 characters.                                                                                                                                                                                                                                          |

## Response

`201` with the full character resource. On a fresh draft:

* `status` is `draft`.
* `views[]` has **eight** entries, every one `status: "missing"`.
* `build.missing_views` lists all eight; `build.cost_credits` is `build.per_view_credits` × 8 — what a build would charge *right now*. Every accepted photo you file takes a view off that list and lowers the quote.

## Limits

* **Cost**: free. A draft renders nothing and charges nothing. You are charged when you [build](/api-reference/characters/build-character).
* **Rate limit**: bucket `characters_post` — 30 requests/min sustained, 60 burst per team. Shared with the other cheap character writes (uploads init/classify, `PATCH`, `DELETE`, view removal, save).
* **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).

## Errors

| Code                                                   | HTTP | When                                                                         |
| ------------------------------------------------------ | ---- | ---------------------------------------------------------------------------- |
| `invalid_request` / `missing_field` / `invalid_format` | 400  | Body validation failure; `param` names the offending field.                  |
| `nudity_not_allowed`                                   | 400  | `nudity: "nude"` on a team that always renders clothed. `param` is `nudity`. |
| `invalid_api_key`                                      | 401  | Missing, malformed, or revoked `X-Api-Key`.                                  |
| `insufficient_scope`                                   | 403  | The key does not carry the `write` scope.                                    |
| `idempotency_key_in_use`                               | 409  | Same `Idempotency-Key` used with a different body or route.                  |
| `too_many_requests`                                    | 429  | Burst > 60 or sustained > 30/min.                                            |
| `provider_unavailable`                                 | 503  | Character rendering is paused for your team; `Retry-After` is set.           |

<Note>
  These changes ship on the existing `2026-08-26` contract. No `Aurous-Version` pin isolates them: the version catalogue carries image and video pricing pointers only, not character pricing or the size of the reference set. Pinning an earlier `Aurous-Version` restores neither the smaller reference set nor the previous price. A character created before 2026-09-14 keeps the references it already has — add either of the two newer views on demand with [`POST /v1/characters/{id}/refs/{view}/regenerate`](/api-reference/characters/regenerate-view); every character created on or after that date carries eight.
</Note>

## Common pitfalls

* A draft is free but not inert — it occupies an id and shows up in `GET /v1/characters`. Clean up the ones you abandon with [`DELETE /v1/characters/{id}`](/api-reference/characters/delete-character), which **hard-deletes** a draft. Photos you uploaded are removed when the draft is deleted or purged; a rejected upload is discarded after its 24-hour ticket window.
* `nudity` is only patchable while `draft`. Decide it here or with `PATCH` before you build; after the build there is no way to change it but to create a new character.
* Sending `attributes` is **not** a substitute for a face once you have uploaded anything. If the draft holds any uploaded photo, one of them must clearly show the face — a faceless body photo plus `attributes` is still [`422 build_requires_identity`](/errors#build_requires_identity). `attributes` stand in for a photo only when the draft has no uploads.
* Read the view vocabulary from [`GET /v1/characters/views`](/api-reference/characters/list-views) and the slot semantics from [Views and lifecycle](/api-reference/characters/views-and-lifecycle); don't hardcode the eight names.


## OpenAPI

````yaml POST /v1/characters/drafts
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` — omit it and the platform default applies (currently
    `2026-08-26`).


    ## 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": "1_5k_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/drafts:
    post:
      tags:
        - Public API (v1) — Characters
      summary: Create a character draft
      description: >-
        Creates a character in `draft`: no credits are charged and nothing is
        rendered. Fill its views with PUT /v1/characters/{id}/refs/{view} (your
        photos, validated for fit), then POST /v1/characters/{id}/build to
        render whatever is missing. A draft cannot be used for generation
        (`character_not_ready`). Set `nudity` here or with PATCH while still a
        draft; it is fixed once the character leaves `draft`. Drafts appear in
        GET /v1/characters (filter with `?status=draft`). Rate limit:
        `characters_post`.
      operationId: V1CharactersController_createDraft
      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). Omit the header to receive
            the platform default, currently `2026-08-26`.
          schema:
            type: string
            example: '2026-08-26'
            pattern: ^\d{4}-\d{2}-\d{2}$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V1CreateCharacterDraftDto'
      responses:
        '201':
          description: Draft created
          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-08-26'
            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 error, or `nudity: "nude"` was requested on a team that
            always renders clothed (code: nudity_not_allowed, param: nudity)
          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-08-26'
            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-08-26'
            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-08-26'
            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 reuse with a different body
          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-08-26'
            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), on
                503 provider_unavailable, and on 409 character_busy. Prefer this
                over computing X-RateLimit-Reset − now.
              schema:
                type: integer
                example: 12
        '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-08-26'
            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), on
                503 provider_unavailable, and on 409 character_busy. Prefer this
                over computing X-RateLimit-Reset − now.
              schema:
                type: integer
                example: 12
        '503':
          description: >-
            Character rendering is paused for this team
            (`provider_unavailable`). Nothing was charged and the character
            keeps the status it had. Retryable — honour 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-08-26'
            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), on
                503 provider_unavailable, and on 409 character_busy. Prefer this
                over computing X-RateLimit-Reset − now.
              schema:
                type: integer
                example: 12
      security:
        - api-key: []
components:
  schemas:
    V1CreateCharacterDraftDto:
      type: object
      properties:
        name:
          type: string
          description: Display name for the character (1-80 chars).
          example: Aurora the Adventurer
          minLength: 1
          maxLength: 80
        nudity:
          type: string
          description: >-
            Whether this character's reference set is nude or clothed. Immutable
            once the character leaves `draft` — set it here, or with PATCH while
            still a draft. Omit to accept your team's default. On teams that
            always render clothed an explicit `nude` returns 400
            `nudity_not_allowed`.
          enum:
            - nude
            - clothed
          example: clothed
        attributes:
          description: >-
            Character attributes. Optional on a draft. They satisfy the
            build-time identity rule ONLY for a draft with no uploaded photos at
            all: once the draft holds any uploaded photo, one of those photos
            must show the face and `attributes` are not consulted — see POST
            /v1/characters/{id}/build.
          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: string
          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. `draft`: references are being assembled; nothing
            has been charged and the character cannot be used for generation.
            New lifecycle states may be added in future — treat any status other
            than `ready` as "not yet usable". `synthesizing`: references are
            being built. `ready`: usable on POST /v1/images — both create flows
            advance here on their own. `reviewing`: reached only after POST
            /:id/resynthesize or POST /:id/build; call POST /:id/save to return
            to `ready`. `failed`: the build failed; `error_message` carries the
            reason and POST /:id/build (builder characters) or POST
            /:id/resynthesize retries. `deleted`: soft-deleted (filtered out of
            the list endpoint).
          example: ready
          enum:
            - draft
            - synthesizing
            - reviewing
            - ready
            - failed
            - deleted
        nudity:
          type: string
          description: >-
            What the reference set is rendered as — `nude` unless you chose
            `clothed`, or — when you omitted `nudity` — your team's default is
            `clothed`. An explicit value always wins. Fixed once the references
            are built.
          enum:
            - nude
            - clothed
          example: clothed
        attributes:
          description: Character attributes. Null when unset.
          nullable: true
          allOf:
            - $ref: '#/components/schemas/CharacterAttributesResponse'
        refs:
          description: >-
            Reference images, one per view the character has. A character built
            today has eight; an older one can have fewer than eight (typically
            six, some of the oldest four). Do not assume a count — read
            `views[]`, the canonical read model for new integrations, or
            `build.missing_views`. `refs[]` lists only the references that
            exist.
          type: array
          items:
            $ref: '#/components/schemas/CharacterRefEntry'
        views:
          description: >-
            One entry per named view, in canonical order — every reference that
            maps to a named view, keyed by view, plus what is missing or was
            rejected. A reference from before named views existed (`refs[].view`
            is `other`) appears only in `refs[]`.
          type: array
          items:
            $ref: '#/components/schemas/CharacterViewEntry'
        build:
          description: What a build would render and cost right now. Always present.
          allOf:
            - $ref: '#/components/schemas/CharacterBuildSummary'
        error_message:
          type: string
          description: >-
            Machine-readable failure code from the most recent
            synthesize/resynthesize attempt. One of `synthesis_failed`,
            `synthesis_timeout`, `provider_unavailable` — a closed set to switch
            on, not customer-facing prose. Null when the most recent attempt
            succeeded (or none has run yet). Set on `failed`, but NOT
            failed-exclusive: a failed *resynthesize* restores the character to
            its prior working status (`reviewing`) rather than overwriting a
            good generation, so a `reviewing` row can carry a non-null code here
            — check this field for failure, not `status`. Cleared on the next
            resynthesize attempt and on success.
          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-08-26'
        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
        - nudity
        - refs
        - views
        - build
        - 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: string
          nullable: true
          example: female
        age:
          type: number
          nullable: true
          example: 28
        ethnicity:
          type: string
          nullable: true
          example: east-asian
        hair_color:
          type: string
          nullable: true
          example: black
        hair_style:
          type: string
          nullable: true
          example: shoulder-length straight
        eye_color:
          type: string
          nullable: true
          example: brown
        body_type:
          type: string
          nullable: true
          example: athletic
        additional_details:
          type: string
          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
        view:
          type: string
          description: >-
            One of the eight named views, or `other` for rows created before
            named views existed. Prefer `view` over `pose` — `pose` is the
            original five-value label kept for compatibility.
          enum:
            - head_front
            - upper_front
            - lower_front
            - upper_back
            - lower_back
            - full_left
            - full_front
            - full_right
            - other
          example: full_front
        source:
          type: string
          description: >-
            `uploaded` — a photo you supplied for this view with PUT
            /v1/characters/{id}/refs/{view}, used as-is and never re-rendered (a
            build skips it, and a per-view regenerate refuses it with 400
            `reference_uploaded`). `generated` — rendered by the platform,
            whether by a create, a build or a regenerate. Both values occur
            today.
          enum:
            - uploaded
            - generated
          example: generated
        url:
          type: string
          description: >-
            Time-limited signed URL for the ref image, on the platform's storage
            host. TTL is 24h — re-fetch the character (GET /v1/characters/{id})
            to mint a fresh URL set when one expires. Treat the host as opaque
            and do not allowlist by hostname: it is not the API host and it is
            not part of the contract. Download the bytes, or re-read the
            character for a fresh URL.
          example: >-
            https://storage-host.example/storage/v1/object/sign/characters/full_front.jpg?token=eyJhbGciOi...
      required:
        - pose
        - view
        - source
        - url
    CharacterViewEntry:
      type: object
      properties:
        view:
          type: string
          description: >-
            The view this slot represents. Always one of the eight; never
            `other`.
          enum:
            - head_front
            - upper_front
            - lower_front
            - upper_back
            - lower_back
            - full_left
            - full_front
            - full_right
          example: full_front
        status:
          type: string
          description: >-
            Slot state. `generated` — holds a platform render. `uploaded` —
            holds a photo you supplied with PUT /v1/characters/{id}/refs/{view}.
            `rejected` — your most recent photo for this slot was refused (see
            `reason`) and no accepted reference took its place; it is surfaced
            while the character is a `draft` and expires 24 h after the attempt,
            at which point the slot reads `missing` again. `missing` — empty; it
            is what a build renders. This is an OPEN set: new states may be
            added, so treat an unrecognized value as `missing`.
          enum:
            - uploaded
            - generated
            - rejected
            - missing
          example: generated
        url:
          type: string
          description: >-
            Time-limited signed URL (24 h TTL) on the platform's storage host
            when `status` is `uploaded` or `generated`; `null` otherwise. Treat
            the host as opaque and do not allowlist by hostname — it is not the
            API host and it is not part of the contract.
          nullable: true
          example: null
        nudity:
          type: string
          description: >-
            Whether this specific reference shows nudity. On a `clothed`
            character EVERY view reads `clothed`, uploaded or generated — the
            read model reports the set's rendering intent there. On a `nude`
            character a generated view reads `nude` and an UPLOADED view reports
            what the photo itself shows, which is how a mixed set arises: a
            clothed body photo filed onto a `nude` character is accepted
            deliberately and reads `clothed` while the character's own `nudity`
            stays `nude`. Compare the two to detect a mixed set. (The reverse is
            refused at intake: a nude photo on a `clothed` character is 422
            `reference_unfit` with `reason: "nudity_mismatch"` — except on
            `head_front`, which carries no nudity rule.) `null` when the slot
            has no reference.
          enum:
            - nude
            - clothed
          nullable: true
          example: clothed
        reason:
          type: string
          description: >-
            Why the most recent upload for this slot was refused. Present only
            when `status` is `rejected`. The same set as the `reason` on a 422
            `reference_unfit`, plus the two byte-level codes (`invalid_format`,
            `value_out_of_range`) a photo can be refused for before the fit
            rules run — those two are error codes rather than fit reasons, so
            the 422 itself carries no `reason` for them. Branch on the value and
            treat anything unrecognized as a generic refusal.
          enum:
            - too_small
            - extreme_aspect
            - no_person
            - multiple_people
            - no_face
            - wrong_view
            - nudity_mismatch
            - invalid_format
            - value_out_of_range
          nullable: true
          example: null
        detected_view:
          type: string
          description: >-
            Which view the refused photo actually looked like. Present only when
            `reason` is `wrong_view` — offer the customer that slot instead of
            asking for a new photo: the same upload ticket can be re-sent to
            that view without re-uploading. `null` on every other slot.
          enum:
            - head_front
            - upper_front
            - lower_front
            - upper_back
            - lower_back
            - full_left
            - full_front
            - full_right
            - other
          nullable: true
          example: null
        made_from:
          description: >-
            Which of your references this view was rendered from, by view name.
            `null` for a view you uploaded (it was not rendered from anything)
            and for a `missing` or `rejected` slot; `[]` for a generated view
            whose provenance was not recorded (rows predating provenance).
            Otherwise the view names it was conditioned on.
          nullable: true
          example:
            - head_front
            - full_front
          type: array
          items:
            type: string
      required:
        - view
        - status
    CharacterBuildSummary:
      type: object
      properties:
        missing_views:
          type: array
          description: >-
            Views with no reference, in canonical view order — what a build (or
            the equivalent per-view regenerates) would render right now.
          example:
            - full_front
            - full_right
          items:
            type: string
            enum:
              - head_front
              - upper_front
              - lower_front
              - upper_back
              - lower_back
              - full_left
              - full_front
              - full_right
        cost_credits:
          type: number
          description: >-
            Credits the next build (or the equivalent per-view regenerates) will
            charge: `per_view_credits` × `missing_views.length`.
            Server-computed, and derived from the references that exist RIGHT
            NOW — so it is meaningful once the character is `ready` or
            `reviewing`. While it is `synthesizing` no view has been persisted
            yet, so this quotes every view still to come, not the remaining
            work.
        per_view_credits:
          type: number
          description: >-
            Current price of one generated view, in credits (also the per-view
            regenerate price). Read it; never hard-code it — it is
            configuration, not a constant. On a `character.completed` webhook
            payload a `0` here means the price could not be read at the moment
            the event was emitted, not that views are free — re-read `GET
            /v1/characters/{id}` for the live value.
      required:
        - missing_views
        - cost_credits
        - per_view_credits
    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
            - character_status_invalid
            - character_busy
            - nudity_not_allowed
            - reference_unfit
            - reference_uploaded
            - build_requires_identity
            - 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
            - reference_check_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
            - character_resynthesize_in_progress
            - 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
        reason:
          type: string
          description: >-
            Sub-code refining `code`, for codes that document one. Present today
            only on `reference_unfit`; `null` otherwise. Branch on `code` first
            and treat an unrecognized `reason` as a generic failure of that
            code.
          enum:
            - too_small
            - extreme_aspect
            - no_person
            - multiple_people
            - no_face
            - wrong_view
            - nudity_mismatch
          nullable: true
          example: wrong_view
        detected_view:
          type: string
          description: >-
            Present only on `reference_unfit` with `reason: "wrong_view"`: the
            view the rejected image actually depicts. Offer the customer that
            view instead of asking for a new photo — the same upload can be
            re-sent to it.
          enum:
            - head_front
            - upper_front
            - lower_front
            - upper_back
            - lower_back
            - full_left
            - full_front
            - full_right
            - other
          nullable: true
          example: full_left
        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_`).

````