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

# Build a character

> Render every view the character is still missing, leaving the photos you uploaded untouched.

`POST /v1/characters/{id}/build` renders every view this character does **not** have a reference for, and leaves the ones you uploaded exactly as they are. It is the second half of the builder: [create a draft](/api-reference/characters/create-draft), [file your own photos](/api-reference/characters/put-ref) into whichever views you have, then build the rest.

The call is **asynchronous**: it returns immediately with `status: "synthesizing"`.

## When to use

* A draft is as complete as you can make it from your own photos and you want the remaining views rendered.
* A previous build **failed** and you want to retry — see [Retrying a failed build](#retrying-a-failed-build).

## Identity

The render needs something to look like. Which rule applies depends on whether you uploaded anything at all — the two are **not** interchangeable:

| The character has…              | What satisfies the identity rule                                                                                                                                                                        |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **at least one uploaded photo** | One of those photos must clearly show the face — the head view, or a full-body front photo. `attributes` are **not** consulted on this path: a faceless body photo plus `attributes` is still refused.  |
| **no uploaded photos at all**   | `attributes` describing the character, set on [`POST /v1/characters/drafts`](/api-reference/characters/create-draft) or with [`PATCH /v1/characters/{id}`](/api-reference/characters/update-character). |

Anything else returns [`422 build_requires_identity`](/errors#build_requires_identity) and charges nothing.

So if you uploaded any photo, one of them must show the face; `attributes` stand in for a photo only when the character has no uploads at all (build accepts `failed` characters too, not only drafts). The fix for a faceless set is another `PUT` — a head-view or full-body-front photo — not more `attributes`. Nothing was charged and nothing changed.

## Price

**`build.per_view_credits` × `build.missing_views.length`**, charged up front. Read both off the character immediately before you build — they are configuration, not constants, and every accepted photo you file moves a view out of `missing_views` and lowers the total. `build.cost_credits` is that product, computed server-side from the references that exist right now.

A render that fails releases its charge.

## Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.aurous-labs.com/v1/characters/char_01HXMQ7Z3K8Y2VNABCDEFGHJKM/build \
    -H "X-Api-Key: $AUROUS_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $(uuidgen)" \
    -d '{"auto_save": true}'
  ```

  ```typescript Node.js theme={null}
  // Read the quote off the character first, then build.
  const before = await fetch(`https://api.aurous-labs.com/v1/characters/${id}`, {
    headers: { "X-Api-Key": process.env.AUROUS_API_KEY! },
  }).then((r) => r.json());

  console.log(before.build.missing_views);                                      // e.g. ["lower_back", "full_right"]
  console.log(before.build.per_view_credits * before.build.missing_views.length); // === before.build.cost_credits

  const character = await fetch(
    `https://api.aurous-labs.com/v1/characters/${id}/build`,
    {
      method: "POST",
      headers: {
        "X-Api-Key": process.env.AUROUS_API_KEY!,
        "Content-Type": "application/json",
        "Idempotency-Key": crypto.randomUUID(),
      },
      body: JSON.stringify({ auto_save: true }),
    },
  ).then((r) => r.json());

  console.log(character.status); // "synthesizing"
  ```

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

  r = requests.post(
      f"https://api.aurous-labs.com/v1/characters/{character_id}/build",
      headers={
          "X-Api-Key": os.environ["AUROUS_API_KEY"],
          "Idempotency-Key": str(uuid.uuid4()),
      },
      json={"auto_save": True},
  ).json()

  print(r["status"])  # synthesizing
  ```
</CodeGroup>

## Body

| Field       | Required | Description                                                                                                                                                                                                                                                         |
| ----------- | -------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `auto_save` |       no | `true` lands the character directly at `ready` when the render finishes, instead of `reviewing`. Use it when you do not run a human review step — you then never call [`POST /v1/characters/{id}/save`](/api-reference/characters/save-character). Default `false`. |

What gets rendered is derived from the views still missing, never from the request body. There is no way to ask for a view the character already has — [regenerate](/api-reference/characters/regenerate-view) it instead.

## Waiting for the result

Poll [`GET /v1/characters/{id}`](/api-reference/characters/retrieve-character) or subscribe to the `character.completed` / `character.failed` [webhooks](/webhooks). Every view renders concurrently, each in roughly 60–180 seconds, so allow a few minutes end to end.

| Terminal status | When                                                                                                                                                                                      |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reviewing`     | The render succeeded and `auto_save` was `false` (the default). Inspect the refs, then call [`POST /v1/characters/{id}/save`](/api-reference/characters/save-character) to reach `ready`. |
| `ready`         | The render succeeded and `auto_save` was `true`. Usable on `POST /v1/images` and `POST /v1/videos` immediately.                                                                           |
| `failed`        | The render failed. `error_message` carries a customer-safe code; the charge is released. See [Retrying a failed build](#retrying-a-failed-build).                                         |

### Nothing missing

If every view already holds a reference, there is nothing to render: the call returns `200` with **no charge and no job**, and the character goes straight to `reviewing` (or `ready` with `auto_save`). The `character.completed` webhook is **still delivered** — a fully-uploaded character produces exactly the same event a rendered one does, with `operation: "create"` like every other character event. Do not special-case it in your receiver.

## Which statuses build

| Character `status`                  | `POST /build`                                                                                                                        |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `draft`                             | Allowed — the normal path.                                                                                                           |
| `failed`                            | Allowed — the recovery path.                                                                                                         |
| `synthesizing`                      | `409 character_busy` with `Retry-After`.                                                                                             |
| `reviewing`, `ready`, anything else | `400 character_status_invalid`, `param: character_id`. The message names the current status and the statuses this operation accepts. |

### Retrying a failed build

`failed` is a deliberate entry point. A builder character's uploaded references are stored **on the character itself** — real references, not expiring upload tickets — so rebuilding after a failure needs no re-upload and no new photos. (They do not outlive the character: photos you uploaded are removed when the draft is deleted or purged; a rejected upload is discarded after its 24-hour ticket window.)

<Note>
  While the character is `failed`, a view can be **replaced** with [`PUT /v1/characters/{id}/refs/{view}`](/api-reference/characters/put-ref) but **not removed**: [`DELETE /v1/characters/{id}/refs/{view}`](/api-reference/characters/delete-ref) requires `draft` and answers `400 character_status_invalid` here. Nothing was lost — swap the photo you want changed, then build again.
</Note>

### One-shot characters cannot build

A character created by [`POST /v1/characters`](/api-reference/characters/create-character) is not builder lineage. Its source photos are still upload **tickets**, not references, so a build would render it from nothing and throw those photos away. It returns `400 character_status_invalid` (`param: character_id`) with a message pointing at [`POST /v1/characters/{id}/resynthesize`](/api-reference/characters/resynthesize-character) — that is the route that reads those tickets. Use resynthesize for a one-shot character, `build` for one you assembled yourself.

## Limits

* **Cost**: `build.per_view_credits` × `build.missing_views.length`, charged before the render is dispatched and released if the render fails. Zero views missing costs nothing.
* **Rate limit**: bucket `characters_synthesize` — 15 requests/min sustained, 30 burst per team. **Shared** across `POST /v1/characters`, `POST /v1/characters/{id}/refs/regenerate`, `POST /v1/characters/{id}/refs/{view}/regenerate`, `POST /v1/characters/{id}/resynthesize` and `POST /v1/characters/{id}/build` — five routes, one bucket. See [Rate limits](/rate-limits).
* **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).

<Note>
  Always send `Idempotency-Key` — a build dispatches one paid render per missing view, so a network retry without a key can double-charge.
</Note>

## Errors

| Code                                                         | HTTP | When                                                                                                                                                         |
| ------------------------------------------------------------ | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `invalid_request` / `invalid_format`                         | 400  | Body validation failure (for example a non-boolean `auto_save`).                                                                                             |
| `character_status_invalid`                                   | 400  | The character is not `draft` or `failed`, or it is a one-shot character that must use `resynthesize`. `param` is `character_id`.                             |
| `invalid_api_key`                                            | 401  | Missing, malformed, or revoked `X-Api-Key`.                                                                                                                  |
| `balance_too_low`                                            | 402  | Team credits below `build.cost_credits`. **Nothing is dispatched and the character keeps its status** — a `draft` is still a `draft`. Top up and call again. |
| `insufficient_scope`                                         | 403  | The key does not carry the `write` scope.                                                                                                                    |
| `resource_not_found`                                         | 404  | Unknown character id, soft-deleted, or cross-team.                                                                                                           |
| `character_busy`                                             | 409  | A render is in flight, or the character changed state underneath the request. Retry after `Retry-After` seconds.                                             |
| `idempotency_key_in_use`                                     | 409  | Same `Idempotency-Key` used with a different body or route.                                                                                                  |
| [`build_requires_identity`](/errors#build_requires_identity) | 422  | No face-bearing reference and no `attributes`. Nothing is charged.                                                                                           |
| `too_many_requests`                                          | 429  | Burst > 30 or sustained > 15/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

* **Never hard-code the price.** Read `build.per_view_credits` and `build.missing_views` off the character immediately before building; both move as you file photos.
* The `200` is not a finished character. It means `synthesizing` — poll or take the webhook before you use the `character_id`.
* Views you uploaded are never re-rendered by a build, and you are never charged for them. That is the point of the builder.
* A zero-cost build still emits `character.completed`. Receivers that assume "an event means a render happened" will be wrong.
* `auto_save: true` skips the `reviewing` stop. If your product shows the customer the rendered set before committing, leave it `false`.


## OpenAPI

````yaml POST /v1/characters/{id}/build
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/{id}/build:
    post:
      tags:
        - Public API (v1) — Characters
      summary: Build the missing views
      description: >-
        Renders every view this character does not have a reference for, and
        leaves the ones you uploaded untouched.


        **Identity** — the render needs something to look like, and the rule
        depends on whether anything was uploaded. If the character holds ANY
        uploaded photo, one of them must clearly show the face (the head view,
        or a full-body front photo) — `attributes` are NOT consulted on that
        path, so a faceless body photo plus `attributes` is still refused.
        `attributes` stand in for a photo only when the character has no
        uploaded photos at all. Otherwise: 422 `build_requires_identity`,
        nothing charged.


        **Price** — `build.per_view_credits` × `build.missing_views.length`,
        charged up front; read both off the character, never hard-code them. A
        render that fails releases its charge.


        **Async** — returns immediately with `status: "synthesizing"`. Poll GET
        /v1/characters/{id} or subscribe to `character.completed` /
        `character.failed`. Pass `auto_save: true` to land at `ready` instead of
        `reviewing` (you then never call POST /{id}/save).


        **Allowed from `draft` and `failed`.** `failed` is the deliberate
        recovery path: a builder character’s uploaded references are stored on
        the character itself, not as expiring upload tickets, so rebuilding
        after a failure needs no re-upload. A character created by POST
        /v1/characters (one-shot) is not builder lineage — it still carries its
        expiring upload tickets and answers 400 `character_status_invalid`
        (param `character_id`) pointing at POST
        /v1/characters/{id}/resynthesize. `synthesizing` answers 409
        `character_busy`; any other status answers 400
        `character_status_invalid` (param `character_id`), naming the current
        status and the statuses this operation accepts.


        **Nothing missing** — 200 with no charge and no job: the character goes
        straight to `reviewing` (or `ready` with `auto_save`) and
        `character.completed` is still delivered.


        **Rate limit:** 15 requests/minute sustained (burst 30) per team —
        **shared** across `POST /v1/characters`, `POST
        /v1/characters/{id}/refs/regenerate`, `POST
        /v1/characters/{id}/refs/{view}/regenerate`, `POST
        /v1/characters/{id}/resynthesize` and `POST /v1/characters/{id}/build` —
        five routes, one bucket. A burst of regenerations, resynthesizes or
        builds 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_build
      parameters:
        - name: id
          required: true
          in: path
          description: Opaque character ID
          schema:
            example: char_01HXMQ7Z3K8Y2NABCDEFGHJKMR
            type: string
        - 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/V1BuildCharacterDto'
      responses:
        '200':
          description: >-
            Build dispatched (`synthesizing`), or terminal immediately when
            nothing was missing
          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 the character is in a non-buildable state
            (`character_status_invalid`, param `character_id`)
          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
        '402':
          description: >-
            Insufficient credits (`balance_too_low`) — nothing is dispatched and
            the character keeps its status
          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
        '404':
          description: Character 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-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: >-
            A render is in flight (`character_busy`, retry after `Retry-After`
            seconds) or Idempotency-Key reuse
          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
        '422':
          description: >-
            Identity rule not satisfied (`build_requires_identity`); nothing is
            charged. Either the character holds uploaded photos and none of them
            shows the face (`attributes` do not substitute here), or it has no
            uploaded photos and no `attributes`
          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
        '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:
    V1BuildCharacterDto:
      type: object
      properties:
        auto_save:
          type: boolean
          description: >-
            When `true`, the character lands directly at `ready` on success
            instead of `reviewing`. Use it if you do not run a human review
            step; you then never call POST /{id}/save. Default `false` (lands at
            `reviewing`).
          example: false
    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
    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_`).

````