Errors
The shape follows your endpoint
An error body is returned in the dialect your SDK parses, decided by the path you POSTed to, never by which sharer or model the request happened to reach.
/v1/chat/completions, /v1/responses and every other OpenAI-style endpoint
(embeddings, images, moderations, OCR, video, audio, music, decisions):
1{2 "error": {3 "message": "…",4 "type": "invalid_request_error",5 "code": "context_length_exceeded"6 }7}
/v1/messages:
1{2 "type": "error",3 "error": { "type": "invalid_request_error", "message": "…" }4}
The Anthropic shape carries no code field. It matches the shape the Anthropic
SDK parses.
/v1/decisions and /v1/systemone use the OpenAI shape with code set to the
HTTP status as an integer, which the OpenRouter SDK requires there.
/v1/rerank and /v2/rerank return both, so Cohere and OpenAI-style clients
can each parse it:
1{2 "message": "…",3 "error": { "message": "…", "type": "invalid_request_error", "code": null }4}
A Claude model on an OpenAI endpoint still returns the OpenAI shape
cc/claude-sonnet-5 on /v1/chat/completions is an openai-node client that
happens to want a Claude model. Keying the error shape on the model or the routed
provider is the specific bug this behaviour exists to avoid.
Statuses
| Status | type | OpenAI code | What it means |
|---|---|---|---|
400 | invalid_request_error | — | Malformed body (one that is not a JSON object reads as one with no model), unknown provider prefix, a bare model id that names no provider (Claude Code and the Codex CLI excepted), a model id prefixed with another provider than the one your key, route or x-aile-provider pins (on a provider whose own ids carry no prefix, such as claude or codex), a refused field, or a bad x-aile-* value. The message names what it knows. On an x402 payment also: an attachment Aile cannot measure, n other than 1, a route or endpoint that takes no output cap, n_predict/n_cmpl on a self-hosted model, or an output budget above what was priced. |
401 | authentication_error | invalid_api_key | No key, or a key that is not recognised. |
402 | api_error | — | Not enough spendable balance (the message says how much and, on chat, where to add funds), a key over its spend limit, or partner credit refused. An attachment Aile cannot measure is held at the model's whole context window, and the message says so. On chat the body is a payment document whose error string says why. |
403 | permission_error | — | The key exists but is not allowed to do this: disabled or expired, a stake key with no window or no usable fallback, or the only sharer left is you (every other sharer is tried first). |
404 | not_found_error | not_found | Unknown model id, a model not served on the provider you named (the message says why), a chat model with no published list price, an unknown video job id, a path that serves nothing, or a chat path the provider you named does not serve (the message names both; refused before your key is checked, with nothing held). |
409 | api_error | — | An Idempotency-Key replay. Deliberately does not fail over. |
413 | invalid_request_error | request_too_large | Body over the size ceiling: 8 MiB, or 25 MiB on transcription, translation, image edits, /v1/images and OCR. |
429 | rate_limit_error | rate_limit_exceeded | Aile's own 429s carry retry-after, except a stake key's shared-pool refusal with no refill time. On self-hosted (local/) chat, a provider's 429 comes back with its status and body but not its headers, so it never carries retry-after. On the non-chat endpoints a provider's 429 is a 502 (below). A rate-window refusal adds x-ratelimit-* and x-aile-refused-by; the concurrency and guard refusals do not. See Rate limits. |
500 | api_error | — | Fault on our side. The message is An unexpected error occurred (request <id>)., the id matching x-request-id; no internal detail is sent. |
502 | upstream_error | — | Every sharer dialled refused, or the provider failed in a way that is not attributable to the request. A self-hosted machine that failed without answering says Self-hosted capacity failed (request <id>). Arrives as 503 (below). |
503 | overloaded | overloaded | No sharer available for what you asked for (the message names the provider), or the relay is restarting, overloaded or paused, or its price list is still loading. Carries retry-after when a clock decides when it clears. |
504 | api_error | — | No answer inside a deadline. Buffered chat names stream: true (unless the request already sent it) and adds x-aile-error: edge_deadline. Nothing is charged. Arrives as 503 (below). |
502 and 504 arrive as 503
Cloudflare replaces an origin 502 or 504 with its own page, so /v1 sends
both as 503 and carries the real code in x-aile-status. The body is the
normal error body.
A keyed image or music request still running after 25 s is committed to 200
with x-aile-edge-hold, so a later failure arrives as a 200 carrying the
error body (on a stream, as an error frame). The same holds for a streamed chat
request; see Streaming.
Non-chat endpoints
Embeddings, images, audio, video, music, rerank, moderations, OCR and decisions use one account per request with no failover. When the provider refuses:
400,404,409,413,415and422pass through with the provider's words, sharer details removed. Fix the request.- A status that belongs to the sharer's account (
401,402,403,429,5xx) is a502sayingupstream X answered HTTP N; you were not charged, with none of the provider's text. On401to403it addsx-should-retry: false, because the same account refuses again. A401that marks the sharer's key dead, or a refusal that takes the sharer's account or model off sale (a spent balance), does not, since a retry picks another sharer. - A
2xxcarrying nothing usable (no image, no audio, no results) is voided and answered502.
Fields refused on chat
A sharer's account is billed for what the provider does, and Aile bills you only
for what it can meter. A request field that would make the provider charge the
sharer outside the metered usage is a 400 naming the field, before your key is
checked or anything is held:
- a
file_id(or a file-search tool, or a hosted tool's container or file reference), in any dialect: it resolves in the sharer's workspace; - on OpenRouter:
openrouter:*server tools, plugins other than the free PDF parser,modelsorroutefallback, a:onlineor:nitromodel, presets, and aprovider.orderorprovider.onlyslug ending/fast,/priorityor/ultrafast(premium-tier endpoints); - on a Claude model: Anthropic's advisor tool, and
inference_geoother thanglobal; - on hosts that bill hosted web search apart from usage (
xkiro,muse-code):web_search,web_search_options, or aweb_search*tool. A function tool you namedweb_searchis fine; - on
xaiandxai-oauth: any tool the provider runs itself (web_search,x_search,code_interpreterand the like),web_search_optionsandsearch_parameterson/v1/chat/completions.
Fast mode (speed) is removed rather than refused on every chat endpoint
(self-hosted local/ models excepted), and
fallbacks on /v1/messages likewise; the reply says so in
x-aile-speed-normalized and x-aile-fallbacks-normalized. See Headers.
Which are worth retrying
Retry: 429 (respect retry-after), 500, 502, and 503 after a delay.
A 503 often means the pool was momentarily empty for that provider rather than
that nobody serves it. Not when it carries x-should-retry: false; the OpenAI
SDKs already honour that header.
Do not retry unchanged: 400, 401, 403, 404, 413. Retrying the same
body produces the same answer.
Never blindly retry: 409. It means a request with that Idempotency-Key
already exists. Read the original result instead.
Failover is already happening
On chat, by the time you see a 503, Aile has already tried several sharers.
x-aile-attempts on a successful response tells you how many it took. A 503
with x-aile-status: 502 means sharers were dialled and every one refused; a
plain 503 means none was eligible.
A retry loop of your own sits on top of a retry loop that already ran, so keep yours short and backed off rather than aggressive.
Note that pre-dispatch refusals are free: a sharer skipped for being in cooldown, having no payout wallet, or being saturated does not consume the attempt budget. Only real dials consume it. That is why a pool whose first several entries are unusable still reaches a healthy sharer behind them.
Actionable classifications
Some non-retryable failures carry a tag:
1x-aile-error: context_overflow
context_overflow maps to the OpenAI code context_length_exceeded. It means the
request will not fit no matter which sharer takes it, so failing over is pointless.
Shorten the input instead.
Served-model mismatch
1x-aile-verified: false2x-aile-verified-model: <what the response actually claimed>
Emitted only on a confirmed mismatch between the model you asked for and the model the response identifies as. When it cannot be determined, these headers are silent rather than guessing; absence is not evidence of a match. See Verification.
Errors during a stream
Once the stream has started, an error arrives as a terminal SSE frame rather than an HTTP status. See Streaming.