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

# Errors

> Stable typed error envelope, 5 types, every code documented.

Every non-2xx response uses the same envelope. Branch retry policy on `type`, branch UX on `code`, surface `message` to humans, and quote `request_id` in support tickets.

```jsonc theme={null}
{
  "error": {
    "type": "invalid_request",
    "code": "balance_too_low",
    "message": "Team available balance is 1.5 credits, generation requires 2.0.",
    "param": null,
    "doc_url": "https://docs.aurous-labs.com/errors#balance_too_low",
    "request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM"
  }
}
```

## Type taxonomy

`type` is one of five values. The `Aurous-Request-Id` response header always carries the same value as `error.request_id` so logging middleware doesn't need to parse the body.

| Type              | HTTP statuses           | Example codes                                                                                                                                                                                                                                                                                                                       |
| ----------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_request` | 400, 402, 409, 413, 422 | `invalid_request`, `missing_field`, `invalid_format`, `value_out_of_range`, `action_not_available`, `style_retired`, `idempotency_key_in_use`, `generation_not_cancellable`, `balance_too_low`, `payload_too_large`, `prompt_blocked`, `reference_blocked`, `output_moderation_rejected`, `output_not_available`, `uploads_expired` |
| `authentication`  | 401, 403                | `missing_api_key`, `invalid_api_key`, `revoked_api_key`, `unsupported_auth_method`, `insufficient_scope`                                                                                                                                                                                                                            |
| `not_found`       | 404, 410                | `resource_not_found`, `forbidden_resource` (404 by intent — no existence leak), `model_not_found` (404), `output_expired` (410)                                                                                                                                                                                                     |
| `rate_limit`      | 429                     | `too_many_requests`, `concurrency_limit_exceeded`                                                                                                                                                                                                                                                                                   |
| `server_error`    | 500, 502, 503, 504      | `internal_error`, `provider_unavailable`, `provider_timeout`, `provider_unknown_error`                                                                                                                                                                                                                                              |

### Recommended retry policy

| Type              | Retry?                 | How                                                                                                                               |
| ----------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_request` | **No.** Fix the input. | `param` indicates the offending field. Don't retry — the same body will fail again.                                               |
| `authentication`  | **No.** Fix the key.   | Re-fetch the key from your secret store; if revoked, mint a new one.                                                              |
| `not_found`       | **No.**                | Resource doesn't exist (or isn't yours). Don't retry.                                                                             |
| `rate_limit`      | **Yes** with backoff.  | Sleep `Retry-After` seconds (or `X-RateLimit-Reset` − now). See [Rate limits](/rate-limits).                                      |
| `server_error`    | **Yes** with jitter.   | Exponential backoff: 1s → 2s → 4s → 8s, max \~30s. With an `Idempotency-Key`, retries are safe — see [Idempotency](/idempotency). |

## One example per type

### `invalid_request` — 400

```bash theme={null}
curl -X POST https://api.aurous-labs.com/v1/images \
  -H "X-Api-Key: $AUROUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"size": "2k_1_1"}'
```

```http theme={null}
HTTP/1.1 400 Bad Request
Aurous-Request-Id: req_01HXMQ7Z3K8Y2VNABCDEFGHJKM
Content-Type: application/json

{
  "error": {
    "type": "invalid_request",
    "code": "missing_field",
    "message": "prompt: must be a string, prompt: must not be empty",
    "param": "prompt",
    "doc_url": "https://docs.aurous-labs.com/errors#missing_field",
    "request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM"
  }
}
```

### `authentication` — 401

```bash theme={null}
curl https://api.aurous-labs.com/v1/balance \
  -H "X-Api-Key: al_live_invalid"
```

```http theme={null}
HTTP/1.1 401 Unauthorized
Aurous-Request-Id: req_01HXMQ7Z3K8Y2VNABCDEFGHJKM
Content-Type: application/json

{
  "error": {
    "type": "authentication",
    "code": "invalid_api_key",
    "message": "API key is invalid or has been revoked.",
    "param": null,
    "doc_url": "https://docs.aurous-labs.com/errors#invalid_api_key",
    "request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM"
  }
}
```

### `not_found` — 404

A 404 covers both "doesn't exist" and "exists but not yours." We never reveal which — same Stripe stance, no existence-leak oracle.

```bash theme={null}
curl https://api.aurous-labs.com/v1/images/img_99999999999999999999999999 \
  -H "X-Api-Key: $AUROUS_API_KEY"
```

```http theme={null}
HTTP/1.1 404 Not Found
Aurous-Request-Id: req_01HXMQ7Z3K8Y2VNABCDEFGHJKM
Content-Type: application/json

{
  "error": {
    "type": "not_found",
    "code": "resource_not_found",
    "message": "Generation not found.",
    "param": null,
    "doc_url": "https://docs.aurous-labs.com/errors#resource_not_found",
    "request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM"
  }
}
```

### `rate_limit` — 429

```http theme={null}
HTTP/1.1 429 Too Many Requests
Aurous-Request-Id: req_01HXMQ7Z3K8Y2VNABCDEFGHJKM
Retry-After: 12
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714752912
Content-Type: application/json

{
  "error": {
    "type": "rate_limit",
    "code": "too_many_requests",
    "message": "Rate limit exceeded for images_post. Retry after 12s.",
    "param": null,
    "doc_url": "https://docs.aurous-labs.com/errors#too_many_requests",
    "request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM"
  }
}
```

Sleep `Retry-After` seconds and retry. Don't hammer — the bucket only refills at the sustained rate.

### `server_error` — 5xx

Treat as transient. Retry with exponential backoff. If you sent an `Idempotency-Key`, the retry is safe even if the original request actually committed.

```http theme={null}
HTTP/1.1 503 Service Unavailable
Aurous-Request-Id: req_01HXMQ7Z3K8Y2VNABCDEFGHJKM
Content-Type: application/json

{
  "error": {
    "type": "server_error",
    "code": "provider_unavailable",
    "message": "Image provider is unavailable. Please retry.",
    "param": null,
    "doc_url": "https://docs.aurous-labs.com/errors#provider_unavailable",
    "request_id": "req_01HXMQ7Z3K8Y2VNABCDEFGHJKM"
  }
}
```

## All error codes

Every error envelope's `doc_url` is `https://docs.aurous-labs.com/errors#<code>`. Each section below has an anchor matching the code so the link lands on the exact paragraph.

### `invalid_request` codes — 400 / 402 / 409 / 413

<h4 id="invalid_request">
  `invalid_request`
</h4>

The request is structurally invalid in a way no more-specific code captures — a malformed body, an unsupported resolution for the resolved model, or a type-guard failure on a request field (for example a non-numeric `duration`). HTTP `400`. Not retryable — fix the input and resubmit; `param` names the offending field when the platform can isolate one.

<h4 id="missing_field">
  `missing_field`
</h4>

A required field was absent from the request body or query string. `param` names the field. HTTP `400`. Don't retry — supply the missing field.

<h4 id="invalid_format">
  `invalid_format`
</h4>

A field is present but malformed (wrong shape, wrong enum value, wrong opaque-ID prefix). `param` names the field. HTTP `400`. Also returned when a reference or context image URL is well-formed but can't be fetched at create time — the host is unreachable, the response isn't a success, it redirects, or the content isn't a valid image. Shape is checked on both estimate and create, but the fetch itself only happens at create, so a URL that estimates cleanly can still fail with this code on `POST /v1/images`.

<h4 id="value_out_of_range">
  `value_out_of_range`
</h4>

A numeric or array-length field is outside its accepted range (e.g. `count > 4`, `reference_image_urls.length > 6`, `width > 4096`). HTTP `400`.

<h4 id="parameter_invalid_combination">
  `parameter_invalid_combination`
</h4>

You sent two fields together that are mutually exclusive (e.g. `size` AND custom `width`/`height` on `POST /v1/images`; or `context_images` together with `lora_id`; or a composition-act id sent as `lora_id` together with a *different* `action_id` — an act id in `lora_id` already pins that act; or a subject — `character_id`/`reference_image_urls` — together with a frame — `first_frame_url`/`last_frame_url` — on `POST /v1/videos`; or `last_frame_url` without `first_frame_url`; or `last_frame_url` together with a pinned `video_lora_id` on `POST /v1/videos` — a **first** frame with a video model is a valid combination, a last frame is not). HTTP `400`. `param` names the offending input. Pick one of the two paths per request. Note: a style `lora_id` **composes** with `action_id` and `subjects` — those combinations are valid and do not return this error.

<h4 id="mutually_exclusive_input">
  `mutually_exclusive_input`
</h4>

You sent two body inputs that the endpoint accepts independently but rejects together (e.g. `character_id` AND `reference_image_urls` on `POST /v1/images` or `POST /v1/videos`; or `context_images` together with any of `subjects`, `character_id`, or `reference_image_urls` — the three input styles are mutually exclusive, pick exactly one; or `action_id` together with `context_images` on `POST /v1/images` — compose a pinned act with subjects, not loose context images). HTTP `400`. Distinct from `parameter_invalid_combination` — this code is reserved for body-input pairs whose semantic disambiguation requires you to pick one (that code covers structurally incompatible pairs instead, e.g. `context_images` + `lora_id`). `param` names the offending input.

<h4 id="missing_field-related">
  `missing_field`
</h4>

(See [`missing_field`](#missing_field) above. Same code is also returned when a half-supplied input is detected — e.g. `width` without `height` on `POST /v1/images`.)

<h4 id="character_not_ready">
  `character_not_ready`
</h4>

You referenced a `character_id` that exists but is not in `status: ready` (it's `synthesizing`, `reviewing`, `failed`, or soft-deleted). HTTP `400`. Wait for the character's `status` to flip via `GET /v1/characters/{id}` or webhook, or pick a different character.

<h4 id="too_many_reference_images">
  `too_many_reference_images`
</h4>

Your `subjects[]` would need more than 10 input images in total to build the composition — a `character` subject counts as 1 image, a `reference` subject counts one per URL in `image_urls`. HTTP `400`. `param` is `subjects`. There is no server-side trimming on this endpoint: remove a subject, or drop some URLs from a reference subject, and resubmit. See the [subjects guide](/guides/subjects#the-10-image-budget) for the full budget rule.

<h4 id="action_not_available">
  `action_not_available`
</h4>

You pinned an `action_id` on `POST /v1/images` (or `POST /v1/images/estimate`) together with a number of subjects the composition act does not support. HTTP `400`. `param` is `action_id`. The message names the counts the act does support — e.g. `"This act supports 1 or 2 subjects; you sent 3."` — read them off the act's `supported_character_counts` in [`GET /v1/actions`](/api-reference/openapi), then resubmit with a matching subject count or a different act. Pinning an act with **zero** subjects is always allowed (the count check is skipped). See the [composition acts guide](/guides/actions#count-matching).

<h4 id="style_retired">
  `style_retired`
</h4>

The style you pinned with `lora_id` on `POST /v1/images` (or `POST /v1/images/estimate`) has been discontinued and no longer generates. HTTP `400`. `param` is `lora_id`. Pick a current style from [`GET /v1/loras`](/api-reference/openapi), or remove `lora_id` to generate without a style. Only a small set of discontinued styles return this error — most retired style ids keep working: they either apply their designated successor style (echoed on the response `style`) or generate without a style and add a `warnings[]` entry with code `style_retired_plain`. See [Create an image → Retired styles](/api-reference/images/create-image#retired-styles).

<h4 id="generation_not_cancellable">
  `generation_not_cancellable`
</h4>

You called `POST /v1/images/{id}/cancel` on a generation that's already terminal (`succeeded`, `failed`, `cancelled`, `expired`, or `moderation_rejected`). HTTP `400`. Idempotent — calling cancel on a row whose hold already resolved is a no-op.

<h4 id="prompt_blocked">
  `prompt_blocked`
</h4>

Pre-dispatch moderation classifier rejected the prompt. HTTP `400`. No row is inserted, so there's nothing to retrieve via `GET /v1/images/{id}`. (A future date-pin will insert a `moderation_rejected` row and fire `image.moderation_rejected` instead — see the [Changelog](/changelog).)

<h4 id="reference_blocked">
  `reference_blocked`
</h4>

Pre-dispatch moderation classifier rejected one of the reference images. HTTP `400`. Same disposition as `prompt_blocked`.

<h4 id="output_moderation_rejected">
  `output_moderation_rejected`
</h4>

Post-generation classifier rejected the output. HTTP `400`. The hold is released; no charge. The reason ID is logged on the inference row.

<h4 id="unknown_version">
  `unknown_version`
</h4>

The `Aurous-Version` header value is not in the published catalog (see the [Changelog](/changelog)). HTTP `400`. Use a date-pin advertised on the changelog or omit the header to fall back to your team default.

<h4 id="balance_too_low">
  `balance_too_low`
</h4>

The team's available balance (`credits` − pending holds) is less than the cost of the requested generation. HTTP `402`. Top up via the dashboard or wait for pending holds to commit/release.

<h4 id="idempotency_key_in_use">
  `idempotency_key_in_use`
</h4>

Returned in three cases, all HTTP `409`: (1) you sent the same `Idempotency-Key` with a **different request body**; (2) you reused the same key across **different routes** (e.g. `/v1/images` and `/v1/videos`); or (3) a request with this key is **still in flight** — the first call hasn't finished yet. For (1) and (2), use a fresh key (or resend the original body to replay). For (3), wait briefly and retry the same key; once the original completes you'll receive its replayed response. See [Idempotency](/idempotency).

<h4 id="payload_too_large">
  `payload_too_large`
</h4>

The request body exceeded the size limit. HTTP `413`. The raw Seedance `POST /v1/contents/generations/tasks` endpoint accepts up to **64 MB** of inline (base64) content; every other endpoint caps at **5 MB**. Reduce the payload — downsample or shrink inline media, or reference it by URL / file upload instead of embedding it — and resubmit.

<h4 id="reference_fetch_failed">
  `reference_fetch_failed`
</h4>

`POST /v1/videos` couldn't fetch your `reference_video_url`/`reference_audio_url`. HTTP `400`. `param` names the offending field; the message carries the specific reason (DNS failure, blocked host, connection timeout, non-2xx response, a redirect — pass the final URL, redirects are refused — or the file exceeded the size cap). Fix the URL (host it somewhere reachable and redirect-free) and retry.

<h4 id="reference_media_invalid">
  `reference_media_invalid`
</h4>

Your `reference_video_url`/`reference_audio_url` was fetched successfully but failed validation — wrong container/format, duration outside 2–15 seconds, or (video only) frame area outside the supported band. HTTP `400`. `param` names the offending field; the message states the specific bound you missed and how to fix it (trim the clip, re-encode as MP4/MOV or WAV/MP3, export at a supported resolution).

<h4 id="first_frame_too_small">
  `first_frame_too_small`
</h4>

Your `first_frame_url` on `POST /v1/videos` is below the minimum frame size for animation: **at least 64 pixels on the shortest side** and **at least 6,400 pixels in total** (so 80×80 is the smallest square accepted, and at a 64-pixel short side the long side must be at least 100). `param` is `first_frame_url`. Not retryable with the same image — the identical bytes fail identically every time. Send a larger frame.

This code reaches you on **two** surfaces, depending on how you supplied the frame:

* **`file_<ulid>`** (from `POST /v1/files`) — HTTP `400` at request time. Dimensions were measured at upload, so the request is rejected before anything is held or charged.
* **`https://` URL** — the dimensions aren't knowable without fetching the bytes, and validation never fetches, so the rule is enforced during preparation instead. The create call returns `201` with `status: "pending"`, then the generation settles at `status: "failed"` with `error_code: "first_frame_too_small"` and the full hold refunded (`cost.refunded: true`). Branch on `error_code` — never on the wording of `error_message`.

The rule applies only when a video model is in play (`video_lora_id` pinned, or omitted so it can auto-match), because that is what turns your still into a seed clip. The check fires on the *possibility* of a model, not on one actually being used — an un-pinned frame is rejected even if no model would have matched your image. With `video_lora_id: null` no seed clip is built and no minimum applies. The same is true when a `last_frame_url` rides alongside your first frame — a first + last pair never auto-matches and builds no seed clip either, so this code never fires on an interpolation request. Check the dimensions client-side if you want a floor on that path. See [Create a video → Minimum frame size](/api-reference/videos/create-video#minimum-frame-size).

### `authentication` codes — 401 / 403

<h4 id="missing_api_key">
  `missing_api_key`
</h4>

No `X-Api-Key` header was sent. HTTP `401`. Add the header — see [Authentication](/authentication).

<h4 id="invalid_api_key">
  `invalid_api_key`
</h4>

The `X-Api-Key` header value is malformed, unknown, or no longer authorized for the requested route. HTTP `401`. Re-fetch the key from your secret store; if it was rotated, mint a new one in the dashboard.

<h4 id="revoked_api_key">
  `revoked_api_key`
</h4>

The key existed but has been revoked. HTTP `401`. Mint a new key in `/dashboard/api-keys`.

<h4 id="unsupported_auth_method">
  `unsupported_auth_method`
</h4>

Your credential is valid, but this endpoint doesn't accept this credential type. HTTP `403` — not `401`, since the credential itself isn't the problem. `message` names the endpoint to call instead. No retry on this endpoint; switch to the named one.

<h4 id="insufficient_scope">
  `insufficient_scope`
</h4>

Your API key is valid, but its scope doesn't permit this route. HTTP `403` — not `401`, since the credential itself isn't the problem. `message` names the required scope. Mint a new key with that scope (or `full`) in the dashboard, or use one that already has it. No retry with the same key.

### `not_found` codes — 404

<h4 id="resource_not_found">
  `resource_not_found`
</h4>

The resource doesn't exist (or doesn't belong to the requesting team — see `forbidden_resource`). HTTP `404`. Don't retry.

<h4 id="forbidden_resource">
  `forbidden_resource`
</h4>

The resource exists but belongs to another team. HTTP `404` (we return 404 instead of 403 — same Stripe stance, no existence-leak oracle).

<h4 id="output_expired">
  `output_expired`
</h4>

The generation reached `status: succeeded` and produced output, but the stored output URL has aged past its retention window. HTTP `410`.

* Image outputs are retained **\~7 days** after generation.
* Video outputs are retained **\~24 hours** after generation.

After that, `GET /v1/images/{id}/output/{n}` and `GET /v1/videos/{id}/output` return `410 Gone` with this code. Save copies of outputs you want to keep — long-term storage is intentionally not part of the platform. To get fresh outputs, create a new generation with the same prompt.

<h3 id="output_not_available_section">
  `output_not_available` (422) — terminal-status-without-output
</h3>

<h4 id="output_not_available">
  `output_not_available`
</h4>

The generation reached a terminal status that never produced output (`failed`, `cancelled`, `moderation_rejected`, or polling-timeout `expired`) — or is still in-flight (`pending` / `processing`). HTTP `422`.

Distinct from `output_expired`: `output_expired` means the URLs once existed and aged out, `output_not_available` means they never existed. Check `GET /v1/images/{id}` (the generation lookup endpoint accepts both `img_*` and `vid_*` IDs — there is no separate `GET /v1/videos/{id}`) for the row's `status` and (when failed) `error_message`, then create a new generation.

<h3 id="uploads_expired_section">
  `uploads_expired` (422) — character's source photos are gone
</h3>

<h4 id="uploads_expired">
  `uploads_expired`
</h4>

Returned by `POST /v1/characters/{id}/resynthesize` when the character was created from uploaded photos (not attributes-only) and the original uploaded photos are no longer available. HTTP `422`. No credits are charged — the eager check runs before billing.

Same "well-formed request, resource state can't satisfy it" precedent as `output_not_available`. This endpoint does not accept new photos on resynthesize, so recovery is `DELETE /v1/characters/{id}` followed by `POST /v1/characters` with fresh `upload_ids`. Characters created via the attributes-only synthesize flow (no uploaded photos) never trigger this code.

### `rate_limit` codes — 429

<h4 id="too_many_requests">
  `too_many_requests`
</h4>

You exceeded the rate limit for this endpoint class. HTTP `429`. Sleep `Retry-After` seconds and try again. See [Rate limits](/rate-limits).

<h4 id="concurrency_limit_exceeded">
  `concurrency_limit_exceeded`
</h4>

You exceeded the per-team concurrent-in-flight cap (default **10** generations in `pending`/`processing`). The raw Seedance surface (`POST /v1/contents/generations/tasks`) has its own **lower** cap — default **5** non-terminal tasks; other generation surfaces default 10. HTTP `429`. This limit is **count-based**, so it carries **no** `Retry-After`: wait for an in-flight task to reach a terminal state (or cancel a queued/pending one), then retry — or apply backpressure in your client.

### `server_error` codes — 5xx

<h4 id="internal_error">
  `internal_error`
</h4>

The platform hit an unexpected condition. HTTP `500`. Retry with exponential backoff. With an `Idempotency-Key`, retries are safe.

<h4 id="provider_unavailable">
  `provider_unavailable`
</h4>

The upstream image / video provider is unhealthy. HTTP `503`. Retry with exponential backoff. Also returned when the API cannot verify billing or account state, or cannot prepare reference images for a generation, due to a transient internal fault — retry per `Retry-After` (typically 5s).

<h4 id="provider_timeout">
  `provider_timeout`
</h4>

The upstream image / video provider didn't return within the platform's polling window. HTTP `504`. Retry with exponential backoff.

<h4 id="provider_unknown_error">
  `provider_unknown_error`
</h4>

The upstream video provider returned an error the platform's mapping table doesn't yet recognize. HTTP `502`. Aurous is alerted so the mapping can be added next iteration; the customer-facing message stays vendor-neutral. Treat as transient — retry with exponential backoff. This is the video counterpart to `chat_provider_unknown_error` / `embeddings_provider_unknown_error`.

<h4 id="chat_provider_unavailable">
  `chat_provider_unavailable`
</h4>

The upstream chat model is temporarily unavailable. HTTP `502`. Any held credits are released. Retry with exponential backoff — with an `Idempotency-Key` on a non-streamed request, retries are safe. See also: [chat\_provider\_unavailable](/api-reference/errors/chat_provider_unavailable).

<h4 id="chat_provider_request_invalid">
  `chat_provider_request_invalid`
</h4>

The platform sent a malformed request to the upstream model. HTTP `500`. Treated as a platform-side bug; engineering is paged. Held credits are released. Retry with backoff. See also: [chat\_provider\_request\_invalid](/api-reference/errors/chat_provider_request_invalid).

<h4 id="chat_provider_auth_failed">
  `chat_provider_auth_failed`
</h4>

The platform's credential with the upstream model failed. HTTP `500`. Not a problem with your `X-Api-Key`. On-call paged. Retry after a short delay. See also: [chat\_provider\_auth\_failed](/api-reference/errors/chat_provider_auth_failed).

<h4 id="chat_provider_unknown_error">
  `chat_provider_unknown_error`
</h4>

The upstream returned an error the platform's mapping table doesn't yet recognize. HTTP `502`. Engineering will add the mapping; treat as transient. See also: [chat\_provider\_unknown\_error](/api-reference/errors/chat_provider_unknown_error).

### LLM chat + embeddings — model, parameter & cancellation codes

<Note>
  Codes in this group span several `type`s — the `type` in each envelope is
  authoritative. Notably `model_not_found` is **`not_found`** (404), not
  `invalid_request`; `model_disabled` is `invalid_request` (403).
</Note>

<h4 id="model_not_found">
  `model_not_found`
</h4>

The `model` slug is unknown for your team. HTTP `404`. List available models with [`GET /v1/models`](/api-reference/openapi#tag/models). See also: [model\_not\_found](/api-reference/errors/model_not_found).

<h4 id="model_disabled">
  `model_disabled`
</h4>

The model exists but has been deactivated. HTTP `403`. Pick a different model from the listing. See also: [model\_disabled](/api-reference/errors/model_disabled).

<h4 id="model_wrong_kind">
  `model_wrong_kind`
</h4>

You sent an embedding model to the chat endpoint, or vice versa. HTTP `400`. Check `aurous_metadata.kind` on each model row. See also: [model\_wrong\_kind](/api-reference/errors/model_wrong_kind).

<h4 id="max_tokens_exceeds_hard_cap">
  `max_tokens_exceeds_hard_cap`
</h4>

`max_tokens` exceeds the model's `max_output_tokens_hard_cap`. HTTP `400`. Lower the request or pick a larger-cap model. See also: [max\_tokens\_exceeds\_hard\_cap](/api-reference/errors/max_tokens_exceeds_hard_cap).

<h4 id="missing_max_tokens_no_model_default">
  `missing_max_tokens_no_model_default`
</h4>

`max_tokens` was omitted on a model with no platform default. HTTP `400`. Pass `max_tokens` explicitly. See also: [missing\_max\_tokens\_no\_model\_default](/api-reference/errors/missing_max_tokens_no_model_default).

<h4 id="max_input_tokens_exceeded">
  `max_input_tokens_exceeded`
</h4>

Prompt is over the model's context window. HTTP `400`. Trim input or pick a larger model. See also: [max\_input\_tokens\_exceeded](/api-reference/errors/max_input_tokens_exceeded).

<h4 id="tool_choice_required_unsupported">
  `tool_choice_required_unsupported`
</h4>

`tool_choice: "required"` requested on a model whose capabilities don't include it. HTTP `400`. Use `tool_choice: "auto"` or pick a capable model. See also: [tool\_choice\_required\_unsupported](/api-reference/errors/tool_choice_required_unsupported).

<h4 id="response_format_too_large">
  `response_format_too_large`
</h4>

JSON schema in `response_format` exceeds the platform's payload cap. HTTP `400`. Trim the schema. See also: [response\_format\_too\_large](/api-reference/errors/response_format_too_large).

<h4 id="response_format_too_deep">
  `response_format_too_deep`
</h4>

JSON schema in `response_format` nests deeper than the parser's cap. HTTP `400`. Flatten via `$defs` references. See also: [response\_format\_too\_deep](/api-reference/errors/response_format_too_deep).

<h4 id="chat_cancel_target_not_found">
  `chat_cancel_target_not_found`
</h4>

The cancel `id` doesn't exist for your team. HTTP `404`. No existence leak across teams. See also: [chat\_cancel\_target\_not\_found](/api-reference/errors/chat_cancel_target_not_found).

<h4 id="chat_cancel_target_already_terminal">
  `chat_cancel_target_already_terminal`
</h4>

The chat completion is already in a terminal state. HTTP `409`. Idempotency hint, not a bug. Read the final-state record. See also: [chat\_cancel\_target\_already\_terminal](/api-reference/errors/chat_cancel_target_already_terminal).

<h4 id="chat_cancel_target_not_cancellable">
  `chat_cancel_target_not_cancellable`
</h4>

The record is non-terminal but the cancel can't take effect (sync call already returned, or in-flight on a different deploy instance). HTTP `409`. See also: [chat\_cancel\_target\_not\_cancellable](/api-reference/errors/chat_cancel_target_not_cancellable).

### LLM embeddings — `invalid_request` codes

<h4 id="embeddings_batch_not_supported">
  `embeddings_batch_not_supported`
</h4>

`input` was sent as an array of pure strings. HTTP `400`. v1.0 multimodal embeddings would concatenate batched text into one combined vector (opposite of OpenAI's N→N semantics), so the platform rejects the shape explicitly. Loop client-side for N→N, or pass a content-parts array for one combined embedding. See also: [embeddings\_batch\_not\_supported](/api-reference/errors/embeddings_batch_not_supported).

<h4 id="embeddings_input_too_many_items">
  `embeddings_input_too_many_items`
</h4>

`input` content-parts array exceeds the per-request caps (16 total parts, 8 image\_url parts). HTTP `400`. Split into multiple requests. See also: [embeddings\_input\_too\_many\_items](/api-reference/errors/embeddings_input_too_many_items).

<h4 id="embeddings_video_unsupported">
  `embeddings_video_unsupported`
</h4>

`video_url` parts are not accepted on v1 embeddings as of 2026-05-24. The provider folds video frames into the visual billing bucket, so the previously published video rate never actually fired — to keep the receipt honest we removed the shape. Submit text or `image_url` parts only; image inputs bill at the visual rate. HTTP `400`. See also: [embeddings\_video\_unsupported](/api-reference/errors/embeddings_video_unsupported).

Renamed 2026-05-24 from `embeddings_video_too_many_parts` (which previously fired only on 2-or-more videos). Integrations that caught the old code on a single-video payload should switch to `embeddings_video_unsupported` and remove the `video_url` part entirely.

### LLM embeddings — `server_error` codes

<h4 id="embeddings_provider_unknown_error">
  `embeddings_provider_unknown_error`
</h4>

The upstream embedding model returned an error the platform's mapping table doesn't yet recognize. HTTP `502`. Engineering will add the mapping; treat as transient and retry with backoff. See also: [embeddings\_provider\_unknown\_error](/api-reference/errors/embeddings_provider_unknown_error).

### LLM chat + embeddings — `rate_limit` codes

<h4 id="tpm_rate_limit_exceeded">
  `tpm_rate_limit_exceeded`
</h4>

Tokens-per-minute bucket exhausted for your team. HTTP `429`. Sleep `Retry-After` seconds; see [Rate limits](/rate-limits). See also: [tpm\_rate\_limit\_exceeded](/api-reference/errors/tpm_rate_limit_exceeded).

<h4 id="provider_rate_limited">
  `provider_rate_limited`
</h4>

The upstream model is throttling. HTTP `503`. Retry with backoff; `Retry-After` is forwarded when available. See also: [provider\_rate\_limited](/api-reference/errors/provider_rate_limited).

## The `doc_url` convention

Every error code has a deterministic deep-link: `https://docs.aurous-labs.com/errors#<code>`. Open it in a browser to land on this page's anchor for the code.

## Using `request_id` for support

Every error envelope and every successful response carries a `request_id` (also surfaced as the `Aurous-Request-Id` response header). When opening a support ticket, **paste at least one `request_id`** so we can pull the exact request from server logs. Example:

> Hi support — getting `provider_timeout` on `POST /v1/images` for the last hour. `request_id: req_01HXMQ7Z3K8Y2VNABCDEFGHJKM`. Team `acme`. Thanks.

This shortcuts triage from "let's look around" to "here's the exact log line."

## Idempotency replay

When you retry with the same `Idempotency-Key`:

* **Success (`2xx`)** is replayed with identical content (field order may differ), carries `Aurous-Idempotent-Replayed: true`, and is never re-billed — even if the original request had already committed.
* **`invalid_request` (`4xx`)** is **not** replayed. `balance_too_low`, `prompt_blocked`, `invalid_format`, and the rest of the `invalid_request` family are client-fixable: a same-key retry **re-evaluates** the corrected request rather than replaying the old rejection. Fix the input (top up, edit the prompt) and retry with the same key.
* **Transient server errors (`5xx`)** are safe to retry with the same key and backoff — the key ensures you won't create a duplicate generation or double-charge if the original had actually committed.

(`idempotency_key_in_use` is the one `invalid_request` code you may see on a retry — it's raised by the idempotency layer itself, not replayed from cache.) See [Idempotency](/idempotency).

## Headers on every response

* `Aurous-Request-Id: req_<ulid>` — quote this in support tickets.
* `Aurous-Version: YYYY-MM-DD` — the API version pin applied to this response.
* `X-RateLimit-Limit` / `X-RateLimit-Remaining` / `X-RateLimit-Reset` — see [Rate limits](/rate-limits).
* `Retry-After` (only on `429`) — seconds to wait before retrying.
* `Aurous-Idempotent-Replayed: true` (only on idempotency-key replays) — see [Idempotency](/idempotency).
