Errors
The error envelopes, status codes and error codes the API returns, and how to fix each.
Errors from the endpoints that reach a model, and from Key, carry x-request-id. Messages errors also carry request-id with the same value as request_id in the body. Quote it when you contact support.
A request that Deference forwarded to a provider appears in Activity under that id, including one the provider failed.
A request that Deference refused before forwarding it, with a 400, 401, 402, 404, 413, 429 or 503 of its own, does not appear there.
GET /v1/models/{id} answers an unknown model with a 404 that carries no request id.
OpenAI envelope
Chat Completions, Responses, Embeddings, Images, Models and Key return this shape.
{
"error": {
"message": "This account has no credit left. Add credit in the dashboard to continue.",
"type": "insufficient_credit",
"code": "insufficient_credit",
"param": null
}
}Anthropic envelope
Messages returns this shape, which Claude Code parses.
{
"type": "error",
"error": {
"type": "billing_error",
"message": "This account has no credit left. Add credit in the dashboard to continue."
},
"request_id": "req_01K7Z4F2XQ"
}Status and type
These types apply to errors generated by Deference. Provider errors follow the rules below.
| Status | OpenAI type | Anthropic type |
|---|---|---|
| 400 | invalid_request_error | invalid_request_error |
| 401 | authentication_error | authentication_error |
| 402 | insufficient_credit | billing_error |
| 403 | permission_error | permission_error |
| 404 | invalid_request_error | not_found_error |
| 409 | api_error | conflict_error |
| 413 | invalid_request_error | request_too_large |
| 410 | api_error | api_error |
| 429 | rate_limit_error | rate_limit_error |
| 502 | api_error | api_error |
| 503 | api_error | overloaded_error |
| 504 | api_error | timeout_error |
Codes
Codes are in error.code in the OpenAI envelope. The Anthropic envelope carries the type only.
| Status | Code | Cause | Fix |
|---|---|---|---|
| 400 | invalid_json | The body is not valid JSON | Send a JSON object |
| 400 | missing_model | The request has no model | Add a model id |
| 400 | missing_prompt | Completions: the request has no prompt | Send a prompt string or array |
| 400 | invalid_parameter | Images: n is not a whole number from 1 to 10 | Send a valid n |
| 400 | output_cap_required | The model lists no maximum output and the request sent no output cap | Send max_tokens on Messages or max_output_tokens on Responses |
| 400 | unsupported_parameter | The body sets models, fallbacks, route or provider, a plugins entry outside the free list (response-healing, file-parser on cloudflare-ai or native, context-compression with enabled: false), a tool type other than function, custom or a supported web-search tool, X search or audio input, or sends a file to a model without file input and no free parser | Name one model and drop those fields. For files, Chat Completions adds the free cloudflare-ai parser itself; Messages, Responses, Embeddings, Images and Completions refuse the file unless the request already sets it |
| 400 | unsupported_model | The model bills per search, such as perplexity/ | Use another model |
| 401 | missing_api_key | No key was sent | Send Authorization: Bearer sk-df-... or x-api-key |
| 401 | invalid_api_key | The key is not recognized | Copy the key again, or create a new one |
| 401 | api_key_disabled | The key is disabled | Enable it in API keys, or use another key |
| 401 | api_key_expired | The key passed its expiry | Create a new key |
| 402 | insufficient_credit | The account has no available credit, or less than the request's hold. A file, or an image or video given as a URL, is held at the larger of the text estimate and the model's context window, or 1,000,000 tokens when that window is unknown | Add credit, lower max_tokens, or send fewer files and URL inputs |
| 402 | key_limit_reached | The key hit its credit limit, or has less left than the request's hold | Raise the limit, lower max_tokens or use another key |
| 402 | free_credit_not_eligible | The account has only free credit, and this model or request does not qualify | Use an open-weight text model, set a lower max_tokens, or add credit |
| 404 | model_not_found | The model id is not in the catalog | List models and pick a current id |
| 413 | request_too_large | The body is over 32 MB, or 64 MB on Images | Send a smaller body |
| 429 | rate_limited | More than 300 requests in a minute on this key, or more than 20 across the keys of an account that has not yet bought or activated credit | Wait retry-after seconds; adding credit lifts the 20-per-minute cap |
| 429 | too_ | This key already has 8 requests in flight | Retry when one finishes |
| 502 | upstream_error | The model provider returned an error, or the connection never opened | Retry; nothing is charged |
| 502 | upstream_error | A non-streamed answer was lost after the provider accepted it | Retry. Nothing is charged yet, and the hold stays reserved |
| 503 | service_unavailable | Inference is temporarily unavailable | Retry shortly |
| 503 | model_prices_stale | The model's prices have not been refreshed for over an hour, so the request cannot be priced | Retry later, or use another model |
| 504 | upstream_timeout | The provider did not answer before the deadline: 120 seconds on Images, 15 minutes on other non-streamed calls, and 5 minutes whenever response headers have not arrived or the body stalls | Retry. Nothing is charged yet, and the hold stays reserved |
free_credit_not_eligible explains itself: its message lists what free credit pays for and suggests max_tokens. See Free credit.
The table covers the model endpoints and Key. The credit-sale GETs answer their own codes — 400 invalid_parameter, 404 not_found, 502 chain_unavailable and 502 quote_unavailable — and the retired routes answer 410 endpoint_retired.
A non-streamed timeout charges nothing yet and keeps its hold reserved. A streamed timeout before response headers returns 504 and costs zero. The OpenAI error.code is upstream_timeout. Messages uses error.type: timeout_. After headers, a timeout interrupts the HTTP 200 stream. A reported cost is charged plus OpenRouter's 5% platform fee. Deference adds no markup. Token counts with a generation id are a provisional estimate. Anything else does not charge the hold.
Provider errors
Provider errors that describe your request keep their status: 400, 403, 404, 408, 409, 413, 422 and 429. Deference rebuilds the body in the endpoint's error envelope and removes provider metadata.
Provider messages are replaced with approved text. Recognized context-length, content-policy, invalid-parameter and unsupported-parameter errors explain how to change the request. Context-length errors keep the phrase prompt is too long, which Claude Code recognizes. Other errors use a fixed message for the status. Provider diagnostics and token counts are not copied into messages.
On Messages, a valid Anthropic error keeps its error.type. Other bodies use Deference's type for the status. A provider 409 without a valid Anthropic error uses conflict_error. The body uses Deference's request_id, also sent in x-request-id and request-id.
A provider 401 or 402, which concerns Deference's own upstream account, returns 503. Any other provider failure returns 502.
These errors carry your request id and never reveal the upstream account.
Failures after a stream starts
A stream that fails midway still returns HTTP 200. When the provider sends an error event it is relayed in the stream; when the upstream connection drops, the stream simply ends with no error event. Activity marks that request failed.
A failed request is charged the provider's reported cost plus OpenRouter's 5% platform fee. Deference adds no markup. Token counts with a generation id are a provisional estimate. Anything else does not charge the hold. See Streaming events.
Retry
Retry 429, 502, 503 and 504 with backoff, and honor retry-after when present. It is a whole number of seconds.
Do not retry 400, 401, 402, 404 or 413 until you fix the cause. See Rate limits.