Skip to main content
Every Aurous-native non-2xx response uses the same envelope below. Branch retry policy on type, branch UX on code, surface message to humans, and quote request_id in support tickets. One route is the exception — see Provider-forwarded errors below for how to recognize it before you branch on type.
Two of those keys are always present but carry a value only where a code documents one — they are null everywhere else:
  • reason — a machine-readable sub-reason for the code. Today only reference_unfit carries one. Branch on code first and treat an unrecognized reason as a generic failure of that code.
  • detected_view — set only on reference_unfit with reason: "wrong_view": the view the rejected photo actually depicts.

Provider-forwarded errors

POST /v1/contents/generations/tasks (the raw Seedance passthrough) is the one place in this API where a non-2xx response does not use the envelope above. When the Seedance provider itself rejects the request in a way that’s yours to fix, Aurous forwards the provider’s own error body verbatim, with the provider’s own HTTP status — see that endpoint’s own docs for when this applies. Only code and message are guaranteed to parse the way they do in the standard envelope; every other field the provider includes — for example its own type — is forwarded unchanged and does not conform to the five-value type enum documented below. This is the literal case the opening paragraph above is qualifying: do not unconditionally branch retry policy on type without first checking which kind of envelope you received. Tell the two apart without needing to remember which route you called:
  • Aurous-native envelope (every route, and every OTHER error on the raw Seedance route itself — structural validation, content-policy rejections, balance, rate limits, etc.): the body always carries request_id and doc_url, and type is always one of the five documented values.
  • Provider-forwarded envelope (raw Seedance route only, provider rejections only): the body has neither request_id nor doc_url — only whatever fields the provider sent (typically code/message, sometimes a type that is not in the enum above). The Aurous-Request-Id response header is still always present either way, so use the header — not the body — if you need a request id off a forwarded error.

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 / 422

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

A content check rejected one of your reference images. This code reaches you on two surfaces, depending on when the check runs:
  • Pre-dispatch — HTTP 400, synchronously, on the POST call itself. Same disposition as prompt_blocked: no row is inserted, so there’s nothing to retrieve via GET /v1/images/{id}.
  • Post-dispatch — the create call returns 201 with status: "pending", then the generation settles at status: "failed" with error_code: "reference_blocked" and the full hold refunded (cost.refunded: true). Branch on error_code — never on the wording of error_message.
The raw Seedance passthrough (POST /v1/contents/generations/tasks) surfaces the identical code on the identical two dispositions, with that route’s own field names: pre-dispatch is the same synchronous 400; post-dispatch settles the task at status: "failed" with a nested error.code: "reference_blocked" (there is no top-level error_code field on that route) and the hold released in full — retrieve it via GET /v1/contents/generations/tasks/{id}. Either way, swap the reference image and resubmit.

output_moderation_rejected

A content check rejected the rendered output — after generation, not before. This is an async-only disposition: there’s nothing to reject synchronously, since nothing has been generated yet. The create call returns 201 with status: "pending", then the generation settles at status: "failed" with error_code: "output_moderation_rejected" and the full hold refunded (cost.refunded: true). Branch on error_code — never on the wording of error_message. The same settlement fires a terminal image.failed (or video.failed) webhook event. No output was ever produced for this id, and none will be — don’t poll waiting for it. GET /v1/images/{id}/output/{n} and GET /v1/videos/{id}/output return 422 (see output_not_available) rather than any media. Retrying with the same Idempotency-Key just replays this same pending snapshot, not a new attempt — use a fresh key, and adjust the prompt or scene if the block recurs.

unknown_version

The Aurous-Version header value is not a known version. HTTP 400. The error message lists every version currently accepted; see the Changelog for what changed on each one. Omit the header to fall back to the platform 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.

character_status_invalid

Returned by the character write routes — PUT /v1/characters/{id}/refs/{view}, DELETE /v1/characters/{id}/refs/{view}, POST /v1/characters/{id}/build, POST /v1/characters/{id}/refs/regenerate, POST /v1/characters/{id}/refs/{view}/regenerate, POST /v1/characters/{id}/resynthesize and POST /v1/characters/{id}/save — when the character’s status does not permit the operation. For example: regenerating a view of a failed character, removing a view from a character that is no longer a draft, or resynthesizing a ready one (resynthesize accepts only reviewing and failed). HTTP 400, param is character_id (on PATCH /v1/characters/{id} changing nudity after the draft stage, param is nudity). The message names the current status and the statuses the operation accepts. Not retryable as sent; change the character’s state (build, resynthesize, save, or delete and recreate) instead. Which status each character operation accepts is tabulated in Views and lifecycle.

error_message on a failed character

Not an API error code — the field a failed character carries on GET /v1/characters/{id} and in the character.failed webhook. It is a closed set of customer-safe codes; branch on it, never on prose: Treat an unrecognized value as synthesis_failed — the set may grow.

character_busy

Returned when a character operation is blocked because a render is in flight (status: synthesizing), on every character write route including PUT/DELETE /v1/characters/{id}/refs/{view} and POST /v1/characters/{id}/build. HTTP 409 with a Retry-After header (seconds). Retryable: wait for the character to reach ready or reviewing (poll GET /v1/characters/{id} or subscribe to character.completed), then repeat the request. DELETE /v1/characters/{id} also returns it for a draft that started building in the same instant — re-read the character and retry.

nudity_not_allowed

You sent nudity: "nude" for a team that always renders clothed — on POST /v1/characters, on POST /v1/characters/drafts, or on PATCH /v1/characters/{id} of a character still in draft. HTTP 400, param is nudity. On such a team no request shape produces a nude reference set — omit nudity (or send clothed) and resubmit. Nothing was dispatched and no credits were held. Contact support if your team’s default is wrong. On a character that is no longer a draft, PATCH refuses nudity before this check ever runs, with 400 character_status_invalid and param: nudity.

reference_unfit

The photo you sent to PUT /v1/characters/{id}/refs/{view} is well-formed but does not fit the view you addressed. HTTP 422 — the platform had to look at the bytes to decide, so you cannot predict it from the request alone. The envelope carries a machine-readable reason (and detected_view when the reason is wrong_view); branch on reason, never on the prose message. Nothing is stored, nothing is charged, and the upload ticket stays usable — re-send the same upload_id to another view without re-uploading the bytes. Those seven are the complete reason set on this code. views[].reason on a character is a superset: it adds invalid_format and value_out_of_range, the two byte-level refusals that arrive as 400s with no reason on the envelope, so the slot can still remember why it is rejected. Keep a default branch on both.

reference_uploaded

You asked to regenerate a view whose reference is a photo you uploaded — POST /v1/characters/{id}/refs/{view}/regenerate or the deprecated POST /v1/characters/{id}/refs/regenerate. HTTP 400, param is view. Your photo is the ground truth that character is rendered from; re-rolling it would discard the identity and charge you for the privilege. Replace it instead with PUT /v1/characters/{id}/refs/{view}, or remove it from a draft with DELETE /v1/characters/{id}/refs/{view} and let a build render it. Nothing was charged.

build_requires_identity

POST /v1/characters/{id}/build has nothing to render the character from. HTTP 422 — the face fact comes from the platform’s own analysis of the photos you filed, so the request alone cannot predict it. Nothing is charged and the character keeps its status. The two cases are not symmetrical, so the fix depends on which one you are in:
  • The character holds at least one uploaded photo and none of them shows the face. attributes are not consulted on this path — sending them will not clear the error. Upload a head-view or full-body-front photo with PUT /v1/characters/{id}/refs/{view} and build again.
  • The character has no uploaded photos at all and no attributes. Set attributes via PATCH /v1/characters/{id}, or upload a face-bearing photo, and build again.

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. The raw Seedance passthrough (POST /v1/contents/generations/tasks) uses the same code for a content[].image_url reference it could not retrieve or use — unreachable host, too many redirects, a content type that isn’t a supported image after sniffing the actual bytes, or over the size limit — HTTP 400, param is content[N].image_url when exactly one reference was submitted or the general content otherwise. Unlike POST /v1/videos above, the message on this route is a fixed, neutral string (“we couldn’t retrieve or use one of your reference images…”) rather than the specific failure reason — echoing which IP was blocked, which status code came back, or the redirect chain involved would turn the error response into a probe of your internal network, so that detail is logged server-side only. This is distinct from reference_blocked on the same route: reference_fetch_failed means the content policy never got a chance to look at the reference at all; reference_blocked means it did, and declined it. Check the URL is publicly reachable over https:// and resolves to a PNG/JPEG/WEBP file within a few redirects, then retry — the same Idempotency-Key re-evaluates the corrected request rather than replaying the old rejection.

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. The raw Seedance route has a second, distinct trigger for this same code: the server-side fetch pool that retrieves your https:// reference images (for content screening) has its own concurrency ceiling, shared across all requests currently in flight — from your team and from others. If it’s saturated, this endpoint returns concurrency_limit_exceeded before attempting the fetch. We don’t expose a separate code for this because the correct client behavior is identical either way: back off and retry shortly. There’s still no Retry-After (this isn’t a token-bucket limit), so a short delay with jitter is the right move for both causes.

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

reference_check_unavailable

The visual fit check behind PUT /v1/characters/{id}/refs/{view} could not run. HTTP 503 with Retry-After: 30seconds, not hours. Nothing was stored, nothing was charged, and your upload ticket is untouched: sleep the Retry-After and send the same request again. This is deliberately a different code from provider_unavailable, which the same route also uses when character rendering is paused for your team — that one carries a 24-hour Retry-After and retrying in 30 seconds will not help. Branch on code, never on the 503 alone.

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.