# 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](https://deference.si/docs/dashboard/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.

```json
{
  "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.

```json
{
  "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/sonar-deep-research`                                                                                                                                                                                                                                                                                                                       | 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_many_concurrent_requests` | 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](https://deference.si/docs/concepts/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_error`. 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](https://deference.si/docs/api-reference/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](https://deference.si/docs/api-reference/rate-limits).
