Skip to main content
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.

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.

One example per type

invalid_request — 400

authentication — 401

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.

rate_limit — 429

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.

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

invalid_request

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.

missing_field

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.

invalid_format

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.

value_out_of_range

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

parameter_invalid_combination

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.

mutually_exclusive_input

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. (See missing_field above. Same code is also returned when a half-supplied input is detected — e.g. width without height on POST /v1/images.)

character_not_ready

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.

too_many_reference_images

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 for the full budget rule.

action_not_available

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

style_retired

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

generation_not_cancellable

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.

prompt_blocked

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

reference_blocked

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

output_moderation_rejected

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

unknown_version

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

balance_too_low

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.

idempotency_key_in_use

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.

payload_too_large

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.

reference_fetch_failed

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.

reference_media_invalid

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

first_frame_too_small

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.

authentication codes — 401 / 403

missing_api_key

No X-Api-Key header was sent. HTTP 401. Add the header — see Authentication.

invalid_api_key

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.

revoked_api_key

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

unsupported_auth_method

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.

insufficient_scope

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

resource_not_found

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

forbidden_resource

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

output_expired

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.

output_not_available (422) — terminal-status-without-output

output_not_available

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.

uploads_expired (422) — character’s source photos are gone

uploads_expired

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

too_many_requests

You exceeded the rate limit for this endpoint class. HTTP 429. Sleep Retry-After seconds and try again. See Rate limits.

concurrency_limit_exceeded

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

internal_error

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

provider_unavailable

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

provider_timeout

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

provider_unknown_error

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.

chat_provider_unavailable

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.

chat_provider_request_invalid

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.

chat_provider_auth_failed

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.

chat_provider_unknown_error

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.

LLM chat + embeddings — model, parameter & cancellation codes

Codes in this group span several types — the type in each envelope is authoritative. Notably model_not_found is not_found (404), not invalid_request; model_disabled is invalid_request (403).

model_not_found

The model slug is unknown for your team. HTTP 404. List available models with GET /v1/models. See also: model_not_found.

model_disabled

The model exists but has been deactivated. HTTP 403. Pick a different model from the listing. See also: model_disabled.

model_wrong_kind

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.

max_tokens_exceeds_hard_cap

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.

missing_max_tokens_no_model_default

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.

max_input_tokens_exceeded

Prompt is over the model’s context window. HTTP 400. Trim input or pick a larger model. See also: max_input_tokens_exceeded.

tool_choice_required_unsupported

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.

response_format_too_large

JSON schema in response_format exceeds the platform’s payload cap. HTTP 400. Trim the schema. See also: response_format_too_large.

response_format_too_deep

JSON schema in response_format nests deeper than the parser’s cap. HTTP 400. Flatten via $defs references. See also: response_format_too_deep.

chat_cancel_target_not_found

The cancel id doesn’t exist for your team. HTTP 404. No existence leak across teams. See also: chat_cancel_target_not_found.

chat_cancel_target_already_terminal

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.

chat_cancel_target_not_cancellable

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.

LLM embeddings — invalid_request codes

embeddings_batch_not_supported

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.

embeddings_input_too_many_items

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.

embeddings_video_unsupported

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

embeddings_provider_unknown_error

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.

LLM chat + embeddings — rate_limit codes

tpm_rate_limit_exceeded

Tokens-per-minute bucket exhausted for your team. HTTP 429. Sleep Retry-After seconds; see Rate limits. See also: tpm_rate_limit_exceeded.

provider_rate_limited

The upstream model is throttling. HTTP 503. Retry with backoff; Retry-After is forwarded when available. See also: 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.

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.
  • Retry-After (only on 429) — seconds to wait before retrying.
  • Aurous-Idempotent-Replayed: true (only on idempotency-key replays) — see Idempotency.