Error codes
A lookup table. For how errors behave and what to do about them, see Errors.
The three shapes
The shape is keyed on the endpoint you called, never on the model or the provider that served.
| Endpoint | Shape |
|---|---|
/v1/chat/completions, /v1/responses, /v1/embeddings, /v1/images/generations, /v1/images, /v1/images/edits, /v1/moderations, /v1/ocr, /v1/videos (and GET /v1/videos/{id}, /content), /v1/videos/generations, /v1/audio/*, /v1/music/generations | OpenAI |
/v1/decisions, /v1/systemone | OpenAI, with code the HTTP status as an integer |
/v1/messages, /v1/messages/count_tokens | Anthropic |
/v1/rerank, /v2/rerank | Cohere + OpenAI |
1{ "error": { "message": "…", "type": "…", "code": "…" } }
1{ "type": "error", "error": { "type": "…", "message": "…" } }
1{ "message": "…", "error": { "message": "…", "type": "…", "code": "…" } }
The Anthropic shape has no code field. In the OpenAI shape code is null
when no override, tag mapping or per-status default applies. Rerank carries
both a top-level message (Cohere clients) and the OpenAI error object.
Decisions put the status in code as a number, because the OpenRouter SDK
validates it as one.
Status table
| Status | type | OpenAI code | Retry? |
|---|---|---|---|
400 | invalid_request_error | null | No; fix the request |
401 | authentication_error | invalid_api_key | No |
403 | permission_error | null | No |
404 | not_found_error | not_found | No |
409 | api_error | null | Never; idempotency replay |
413 | invalid_request_error | request_too_large | No; shrink the body |
429 | rate_limit_error | rate_limit_exceeded | Yes; honour retry-after |
500 | api_error | null | Yes, backed off. The message names the request id (x-request-id) and nothing else |
502 | upstream_error | null | Yes, backed off, unless x-should-retry: false |
503 | overloaded | overloaded | Yes, after a delay; honour retry-after when present |
504 | api_error | null | Buffered chat without stream: true: resend with it. Otherwise yes, backed off |
502 and 504 arrive as 503
Cloudflare swallows an origin 502 or 504, so /v1 sends both as 503 with
the real code in x-aile-status. The body is unchanged.
413 on the Anthropic endpoint
Anthropic's own wire type for 413 is request_too_large; this surface
returns the broader invalid_request_error category there. This is harmless in practice
(the Anthropic SDK branches on the status), and a 413 needs a very large body to
reach at all.
x-aile-error tags
Set on non-retryable failures where the classification is actionable.
| Tag | OpenAI code | Meaning |
|---|---|---|
context_overflow | context_length_exceeded | The request will not fit on any sharer. Failing over is pointless; shorten the input. |
edge_deadline | null | A buffered chat request ran past the deployment's deadline (a 504). Nothing was charged; send stream: true if the request did not. |
402 and payment
A 402 carries PAYMENT-REQUIRED (base64 JSON) describing what must be paid, and the
same x402 document as its JSON body. Answer it with PAYMENT-SIGNATURE; the settlement
result comes back as PAYMENT-RESPONSE. See Headers. A keyed
request to a non-chat endpoint gets an ordinary error body instead.
Where the deployment enables MPP, a priced 402 also carries WWW-Authenticate: Payment
challenges, one per method that can pay, and its body gains the problem fields type,
title, status and detail beside the x402 document. Answer it with
Authorization: Payment (or Payment-Authorization on a chat request made with a key); a
settled payment returns Payment-Receipt. See
Pay with a wallet.
An empty accepts is a well-formed challenge. It means no payment scheme will take this
price: the facilitator publishes a settlement minimum and the call is quoted below it, so
signing is not an option and a PAYMENT-SIGNATURE sent anyway is ignored rather than refused.
Add balance and retry. The error string names that route, and nothing else will clear it.
Mid-stream errors
Once a stream has started there is no HTTP status left to set, so the failure arrives as a terminal frame in the dialect you are reading.
OpenAI:
1data: {"error":{"message":"…","type":"api_error","code":null}}23data: [DONE]
Anthropic:
1event: error2data: {"type":"error","error":{"type":"api_error","message":"…"}}
type and code default generically here, because a mid-stream failure has no
single status to key off.
A short stream is not necessarily a complete one
If you accumulate deltas without inspecting frames, a mid-stream failure reads as a short answer. Both SDKs raise these as exceptions when you use their streaming helpers.
Routing-specific messages
| Situation | Status | The message names |
|---|---|---|
| Bare model id with no provider (except from Claude Code or the Codex CLI, or under any provider) | 400 | the <provider>/<model> form and x-aile-provider |
auto/ followed by a provider, local/ or nothing; beside one provider in x-aile-provider; as a route target; or on a non-chat endpoint | 400 | the auto/<model> form, or <provider>/<model> |
| Any provider: no provider lists the bare id / none has a sharer online | 404 / 503 | the model |
A model on a provider the key or x-aile-exclude-provider excludes | 400 | the provider, and where it is excluded |
| Unknown provider prefix | 400 | the provider ids it does know |
A key pin, route or x-aile-provider naming one provider, and a model id prefixed with another, on a provider whose own ids carry no provider prefix (claude, codex, …) | 400 | both providers, and the fix |
No model, or a body that is not a JSON object | 400 | the missing model |
A chat field that would bill the sharer outside metered usage (file_id, OpenRouter server tools, paid plugins, models/route fallback, :online, :nitro, presets, premium-tier provider.order/provider.only slugs (/fast, /priority, /ultrafast), a file_id on an input_audio/image_url/video_url part, hosted web search where the host bills it apart, Anthropic's advisor tool, inference_geo other than global) | 400 | the field |
A path the named provider does not serve, or a path no endpoint maps (/v1/completions) | 404 | the provider and the path. Refused before the key check; nothing is held |
| Model excluded on the provider you named | 404 | why it is not served there |
| Model with no published list price (self-hosted too) | 404 | the model, and that it is not served |
| Price list not loaded or unreadable | 503 | price list warming; retry or could not read the price list; retry |
| Known provider, no sharer available | 503 | that provider |
Bad x-aile-provider | 400 | GET /v1/models |
Malformed x-aile-max-price, x-aile-min-price or a sharer list | 400 | the expected forms |
| A sharer list or price floor leaves no sharer for the model | 503 | the list or floor, and whether the key or the header set it (refusedBy) |
| Only your own capacity left | 403 | that caller and sharer look like the same party |
| Self-hosted capacity failed without an answer | 502 | the request id only |
| Non-chat provider refused for the sharer's account | 502 | upstream X answered HTTP N; you were not charged |
There is no cross-provider fallback, so a provider you named either serves you or the request fails; only any provider, which you opt into, tries several. See Model names.