> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fortytwo.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> The error envelope, every code the API returns, and which ones to retry.

Every error from `GET /v1/models` and `POST /v1/chat/completions` uses the same JSON envelope. The error event inside a stream has no `param` — see [Streaming](/docs/streaming#errors-during-a-stream). Branch on `error.code`, never on `error.message`.

A path the API does not serve returns a bare status with an empty body, before the key is checked: `404` for an unknown path under `/v1` (for example `/v1/embeddings` or `/v1/models/{id}`), and `405` for a method the path does not accept. Check the status before you parse the body.

## The error envelope

```json theme={null}
{
  "error": {
    "message": "The API key is missing or not valid.",
    "type": "invalid_request_error",
    "param": null,
    "code": "invalid_api_key"
  },
  "request_id": "<value of the x-request-id header>"
}
```

<ResponseField name="error.code" type="string">
  The identifier your client branches on. Every code is in the table below.
</ResponseField>

<ResponseField name="error.type" type="string">
  The broad class: `invalid_request_error`, `insufficient_quota`, `permission_error`, `rate_limit_error` or `server_error`.
</ResponseField>

<ResponseField name="error.message" type="string">
  A description for a person to read. It can change; do not parse it.
</ResponseField>

<ResponseField name="error.param" type="string | null">
  The request field the error refers to, or `null`.
</ResponseField>

<ResponseField name="request_id" type="string">
  The same value as the `x-request-id` header, which every response carries. Include it in a support request.
</ResponseField>

## Codes

| HTTP | `type` | `code` | Meaning | Retry |
| -: | - | - | - | - |
| 400 | `invalid_request_error` | `invalid_request` | The body is not valid JSON, or a field is unknown, malformed or not supported by the model | No |
| 400 | `invalid_request_error` | `unsupported_content_type` | A message has a content part the API does not accept, such as audio or a file | No |
| 400 | `invalid_request_error` | `zdr_storage_not_supported` | `store: true` was sent | No |
| 401 | `invalid_request_error` | `invalid_api_key` | The key is missing, malformed, unknown, disabled, revoked or expired | No |
| 402 | `insufficient_quota` | `payment_hold` | The usable balance is spent and the remaining credits are on hold after a payment dispute | No |
| 402 | `insufficient_quota` | `account_spend_limit_reached` | The account's **Monthly spend limit** is reached | No |
| 403 | `permission_error` | `account_suspended` | The account is suspended | No |
| 403 | `permission_error` | `account_restricted` | The account cannot use the API: its email is not verified, or it is being closed | No |
| 404 | `invalid_request_error` | `model_not_found` | The model ID is unknown or not available | No |
| 408 | `invalid_request_error` | `request_timeout` | The request body did not arrive within 55 seconds | No |
| 413 | `invalid_request_error` | `request_too_large` | The body or an image is over the [size limit](/docs/limits#request-size) | No |
| 415 | `invalid_request_error` | `unsupported_content_type` | The `Content-Type` header is not `application/json` | No |
| 429 | `insufficient_quota` | `insufficient_quota` | The balance is spent | No |
| 429 | `rate_limit_error` | `billing_limit_exceeded` | The key's **Spend limit** is reached | No |
| 429 | `rate_limit_error` | `rate_limit_exceeded` | The requests per minute or per hour for this model are used up | Yes |
| 429 | `rate_limit_error` | `concurrency_limit_exceeded` | Too many requests from the account are running at once | Yes |
| 429 | `server_error` | `capacity_exhausted` | No capacity for the model right now | Yes |
| 502 | `server_error` | `upstream_error` | The request failed after it started | See below |
| 503 | `server_error` | `admission_unavailable` | The service cannot accept the request right now | Yes |
| 503 | `server_error` | `server_draining` | The server you reached stopped taking new requests | Yes |
| 504 | `server_error` | `upstream_timeout` | The request reached its [time limit](/docs/limits#how-long-a-request-may-run) | See below |

`GET /v1/models` returns only `401`, `403` and `503` codes from this table.

After a stream has started, every failure arrives as the last event with the code `upstream_error` — see [Streaming](/docs/streaming#errors-during-a-stream).

What to do about each code is in [Common problems](/docs/support-common-problems#api).

## Retries

A code marked **Yes** always comes with a `Retry-After` header, in seconds. Wait at least that long, then send the request again. `rate_limit_exceeded` can ask for up to 3,600 seconds; the others ask for 60 seconds or less. A refused request is not queued: nothing runs unless you send it again.

A code marked **No** is not cleared by retrying. Change the request, the account or the limit first — see [Common problems](/docs/support-common-problems#api).

`502` and `504` happen after the request started, so part of it may have run and be charged — see [What is billed](/docs/pricing#what-is-billed). A retry is a new request and is charged separately.

Every other error is returned before generation starts and is not charged.

There is no idempotency key, and a response that broke off is not resumed.

## Retry sample

```python theme={null}
# pip install openai
import os
import time

from openai import OpenAI, APIStatusError

client = OpenAI(
    api_key=os.environ["FORTYTWO_API_KEY"],
    base_url="https://api.fortytwo.network/v1",
    max_retries=0,  # retry by error code below, not by status
)

RETRY_CODES = {
    "rate_limit_exceeded",
    "concurrency_limit_exceeded",
    "capacity_exhausted",
    "admission_unavailable",
    "server_draining",
}
MAX_WAIT = 60  # seconds; give up rather than wait longer


def create(messages, attempts=4):
    for attempt in range(1, attempts + 1):
        try:
            return client.chat.completions.create(
                model="qwen/qwen3.8-27b", messages=messages
            )
        except APIStatusError as error:
            code = error.code  # the SDK reads error.code from the envelope
            wait = int(error.response.headers.get("Retry-After", "0"))
            if code not in RETRY_CODES or attempt == attempts or wait > MAX_WAIT:
                raise
            time.sleep(wait)


print(create([{"role": "user", "content": "Hello."}]).choices[0].message.content)
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.