---
title: Errors
description: The envelope a refusal arrives in, the status codes the gateway answers with, and the two behaviors every client must handle.
keywords: errors, status codes, 401, 429, envelope, streaming, failover
group: gateway
---

## Quick {#quick keywords="codes, what to do, retry, 429, 503"}

The whole decision table. The detail behind each row is in the sections below.

:::matrix
| Status | Why | What to do |
| --- | --- | --- |
| `400` | The request is not valid — a malformed body, or a member this surface does not take. | Fix the body. There is nothing to retry: the same request is refused the same way. |
| `401` | The request is not authenticated. | Check the header and the key itself. Do not retry. |
| `402` | Nothing to pay with: the wallet is empty, or the package the key spends from is exhausted. | Top up the wallet or the package, or switch the key to the balance. Do not retry. |
| `403` | The key may not call this model, or the organization is frozen. | Check the key's scope and the organization's state in the console. Do not retry. |
| `404` | No such model. | Check the name against the published catalog. Do not retry. |
| `429` | The call went over a rate or spending ceiling. | Back off and retry later, lengthening the wait with every attempt and capping their number. |
| `500` | The request could not be completed. | Back off and retry, not at once. Not blindly in a loop: the outcome upstream may have stayed unknown. |
| `503` | No provider can currently serve this request. | Back off and retry later. Retrying at once does not help: nothing upstream has changed. |
:::

No refusal is fixed by retrying immediately. A client that treats every error as "try again now" spends its whole allowance on being refused.

## The refusal envelope {#envelope keywords="error, envelope, type, message, param, json"}

A refused call answers with one JSON object carrying a single `error` member. It is this protocol's own envelope and not a translation of another's: the same shape refuses a Chat Completions call, a Responses call and a Messages call, and the Messages surface wraps it with the top-level `"type": "error"` its own clients look for.

Two members are always present — `type` and `message` — and two more appear when there is something to say.

| Member | What it carries |
| --- | --- |
| `type` | The class of failure, in this protocol's own closed vocabulary: invalid_request_error, not_found_error, authentication_error, permission_error, rate_limit_error, api_error, and on the Messages surface overloaded_error as well. |
| `message` | What went wrong, in a fixed safe sentence. It is never built from anything the request carried, so it can never quote a prompt, a header or a key back at a log. |
| `code` | The machine-readable reason. One reason has one spelling across every surface of this platform, which is what makes it the thing a client branches on. |
| `param` | The request member at fault, when one member is at fault. |

Branch on `type` and on `code`, never on the wording of `message`: the wording is chosen to be safe to print, not to be parsed.

```json title=Refusal
{
  "error": {
    "type": "authentication_error",
    "message": "The request is not authenticated."
  }
}
```

## Status codes {#error-codes keywords="400, 401, 402, 403, 404, 429, 500, 503, status"}

| Status | What it means |
| --- | --- |
| `400` | The request is not valid — a malformed body, or a member this surface does not take. |
| `401` | The request is not authenticated. |
| `402` | Nothing to pay with: the wallet is empty (`insufficient_wallet_balance`) or the key's package is exhausted (`insufficient_package_balance`). Refused before any supplier is asked. |
| `403` | The key may not call this model, or the organization is frozen. |
| `404` | No such model. |
| `429` | A rate or spending limit refused the request. |
| `500` | The request could not be completed. |
| `503` | No provider can currently serve this request. |

Two of them are worth telling apart before you write a retry. A `429` is a ceiling you are over, and time is what clears it; a `503` means nothing upstream can serve the call at this moment. Neither is fixed by retrying at once, and a client that treats every refusal as "try again immediately" spends its whole allowance on being refused.

## One 401, whatever is wrong with the key {#key-refusals keywords="401, unauthorized, revoked, expired, unknown"}

A missing key, a malformed key, an unknown key, a revoked key and an expired key are answered alike — the same `401`, the same sentence — and that is a decision rather than an omission. Telling them apart would turn the gateway into an oracle over key material: whoever can see the difference between "there is no such key" and "that key was revoked" is reading the platform's key table one guess at a time.

So a `401` says exactly one thing: this call was not admitted. Check that the header is there and spelled as a bearer token, that the key is the whole secret you copied when you minted it, and that the key is still live. The key check answers all three at once, from a browser, without a line of code.

> [Check your key →](/en/key-check) [How the header is written →](/en/authentication)

## A stream that stops early {#stream-failure keywords="streaming, sse, done, truncated, incomplete"}

A refusal decided before the first event is an ordinary JSON error with a status, exactly as a unary call would be — the envelope above, and nothing streamed. A failure after the stream has started cannot be that: the status line has already gone out. Such a stream simply ends, **without** its terminal `[DONE]` frame.

That absence is the whole signal, so read for it. A client that treats "the connection closed" as "the answer finished" will hand a truncated answer to whatever comes next, and nothing in the bytes it received says otherwise. Treat a stream that ends before `[DONE]` as a failed call rather than a short one.

The other two surfaces say the same thing in their own events. On the Messages surface the failure arrives as this protocol's error event and the stream then ends without `message_stop`; on the Responses surface the terminal event is `response.failed`, carrying this envelope in place of the finished body.

## Failover {#failover keywords="failover, channel, upstream, provider, retry"}

A model is usually reachable through more than one channel. When the one serving a call fails before any of the answer has reached you, the call carries on over the next channel for the same model rather than coming back to you.

Three kinds of failure end the call instead of moving it: a failure after the first bytes have reached you, because you cannot be handed a second answer halfway through one you are already reading; a call you canceled, because you are no longer waiting for it; and a call whose outcome upstream is not known, because retrying that one could bill you twice for work that in fact succeeded.

So a `503` is not a promise that every route was tried. It also arrives when no channel is eligible for what the call asked for, before anything upstream is contacted at all, and the number of channels a single call will try is bounded rather than exhaustive.

> [What bounds your throughput →](/en/limits)
