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.
Recommended retry policy
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
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’sdoc_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.
missing_field
(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>(fromPOST /v1/files) — HTTP400at 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 returns201withstatus: "pending", then the generation settles atstatus: "failed"witherror_code: "first_frame_too_small"and the full hold refunded (cost.refunded: true). Branch onerror_code— never on the wording oferror_message.
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.
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 — gettingThis shortcuts triage from “let’s look around” to “here’s the exact log line.”provider_timeoutonPOST /v1/imagesfor the last hour.request_id: req_01HXMQ7Z3K8Y2VNABCDEFGHJKM. Teamacme. Thanks.
Idempotency replay
When you retry with the sameIdempotency-Key:
- Success (
2xx) is replayed with identical content (field order may differ), carriesAurous-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 theinvalid_requestfamily 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 on429) — seconds to wait before retrying.Aurous-Idempotent-Replayed: true(only on idempotency-key replays) — see Idempotency.

