---
title: "Errors"
description: "Handle pirouter API errors by error.code: the HTTP status, meaning, fix and retry rule of each code, plus the codeless 502, 413 and 503 and the errors passed on from the provider."
canonical: "https://pirouter.ai/docs/api/errors"
last-updated: "2026-10-17"
lang: "en"
source: "content/docs/en/api/errors.mdx"
---

# Errors

Handle pirouter API errors by error.code: the HTTP status, meaning, fix and retry rule of each code, plus the codeless 502, 413 and 503 and the errors passed on from the provider.

When a request fails, branch on `error.code` in the response body: it names the exact failure, more precisely than the HTTP status, and it does not change. Retrying a `400`, `401`, `404`, `413` or `422` does not help; a `402` needs credit first, a `429` needs a wait of `Retry-After` seconds, and a `5xx` can be retried with a growing wait.

## The error body

The inference endpoints of all three protocols return errors in one shape:

```json
{
  "error": {
    "message": "No such model. Model names are vendor-qualified (vendor/model), so a name copied from a provider's own docs needs its vendor prefix added. Call GET /v1/models for the slugs this gateway serves.",
    "type": "not_found_error",
    "code": "model_not_found"
  }
}
```

Branch on `error.code`, which is a stable string. `error.message` is written for a person to read and its wording can change, so do not match on it.

Every response carries an `X-Pi-Trace-Id` header, errors included. Quote its value when you report a problem.

The model catalog endpoints return a shorter shape, shown in the [API overview](https://pirouter.ai/docs/api.md#errors).

## Error codes

| HTTP  | `error.code`                                                                                | Meaning                                                                              | What to do                                                                      |
| ----- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
| `400` | [`invalid_request`](https://pirouter.ai/docs/api/errors.md#invalid_request)                 | A required field is missing from the request body.                                   | Add the missing field. Do not retry as is.                                      |
| `400` | [`context_length_exceeded`](https://pirouter.ai/docs/api/errors.md#context_length_exceeded) | The input is clearly larger than the context limit of the model.                     | Shorten the input or pick a model with a larger context. Do not retry as is.    |
| `401` | [`invalid_api_key`](https://pirouter.ai/docs/api/errors.md#invalid_api_key)                 | No key was sent, or the value is not a pirouter key.                                 | Check which header carries the key, and that its variable is set. Do not retry. |
| `401` | [`api_key_not_found`](https://pirouter.ai/docs/api/errors.md#api_key_not_found)             | The key has the right format but no such key exists.                                 | Check that the key was copied in full. Do not retry.                            |
| `401` | [`api_key_revoked`](https://pirouter.ai/docs/api/errors.md#api_key_revoked)                 | The key was revoked.                                                                 | Create a new key in the console. Do not retry.                                  |
| `401` | [`api_key_expired`](https://pirouter.ai/docs/api/errors.md#api_key_expired)                 | The key is past its expiration.                                                      | Create a new key in the console. Do not retry.                                  |
| `402` | [`insufficient_balance`](https://pirouter.ai/docs/api/errors.md#insufficient_balance)       | The wallet balance cannot cover the request.                                         | Top up, then send again. Do not auto-retry.                                     |
| `404` | [`model_not_found`](https://pirouter.ai/docs/api/errors.md#model_not_found)                 | The model ID is unknown or not enabled.                                              | Fix the model ID; GET /v1/models lists valid ones. Do not retry.                |
| `404` | [`no_eligible_provider`](https://pirouter.ai/docs/api/errors.md#no_eligible_provider)       | The routing settings exclude every provider of the model.                            | Loosen the provider field or the routing settings. Do not retry as is.          |
| `422` | [`validation_failed`](https://pirouter.ai/docs/api/errors.md#validation_failed)             | The request breaks a rule: the wrong endpoint for the model, or a bad routing field. | Fix what the message names. Do not retry as is.                                 |
| `429` | [`rate_limited`](https://pirouter.ai/docs/api/errors.md#rate_limited)                       | Requests are arriving faster than the rate limit allows.                             | Retry after the seconds in Retry-After.                                         |
| `500` | [`internal_error`](https://pirouter.ai/docs/api/errors.md#internal_error)                   | An unexpected failure on the pirouter side.                                          | Retry with growing waits.                                                       |
| `503` | [`provider_unavailable`](https://pirouter.ai/docs/api/errors.md#provider_unavailable)       | No healthy provider can serve the model right now.                                   | Retry with growing waits, or list fallback models.                              |

Each code below has its own section: what it means, then how to fix it and whether to retry.

### `invalid_request`

The request body is missing a field its protocol requires. Today that is the model: a body with neither `model` nor `models` returns `400` with the message `model is required`. A call to an endpoint pirouter does not serve (such as `POST /v1/embeddings`) returns the same error; the "Not available today" section of each protocol page lists those endpoints.

Add the field, or use an endpoint pirouter serves. Retrying the same request does not help.

### `context_length_exceeded`

The input is clearly larger than the context limit of every provider that could serve the model, so pirouter refuses the request before sending it anywhere, and it is not billed. The `message` gives the estimated input size and the limit. The size is an estimate, so only a clear overrun is refused: a request near the limit is sent on, and the provider decides.

Shorten the input or choose a model with a larger context. Retrying the same request returns the same error.

### `invalid_api_key`

No key was sent, or the value does not start with `sk-pi-`.

Check which header carries the key and that the environment variable is set. [Send the key with a request](https://pirouter.ai/docs/authentication.md#send-the-key-with-a-request) lists the headers the gateway reads. Retrying does not help until the key is fixed.

### `api_key_not_found`

The value looks like a pirouter key, but no such key exists. Check that the whole key was copied. Retrying does not help until the key is fixed.

### `api_key_revoked`

The key was revoked. Revocation is permanent, so create a new key in the console.

### `api_key_expired`

The key is past the expiration chosen when it was created. Create a new key in the console.

### `insufficient_balance`

The wallet balance cannot cover the estimated cost of the request. [Add credit](https://pirouter.ai/docs/guides/billing.md#add-credit), then send the request again. [When the balance runs out](https://pirouter.ai/docs/guides/billing.md#when-the-balance-runs-out) has the details.

> **Warning:** Do not retry a `402` automatically: the answer stays `402` until the balance changes.

### `model_not_found`

The model ID is unknown or not enabled. Model IDs are written as `vendor/model`, so a name copied from a vendor's own documentation needs the vendor prefix. `GET /v1/models` lists the IDs you can call. See [Model IDs](https://pirouter.ai/docs/models.md#model-ids).

A model that has been taken offline returns the same `404` and code. Its `message` gives the date it went offline and where to choose another model. Calling it through an alias, or naming it as a fallback in `models`, returns the same error.

Use a model ID that is available. Retrying the same request does not help.

### `no_eligible_provider`

The model exists, but the request's `provider` field together with the workspace routing settings excludes every provider that serves it.

Retrying does not help; change the settings instead. `error.metadata` in the body lists the providers that serve the model and the step that removed the last one. See [When no provider is left](https://pirouter.ai/docs/guides/routing.md#when-no-provider-is-left).

### `validation_failed`

The request can be read but breaks a rule. There are two causes, and the `message` says which:

- The model is not served on the endpoint you called. The `message` lists the endpoints it is served on. See [Protocol mismatch](https://pirouter.ai/docs/models.md#protocol-mismatch).
- A routing field (`provider`, `models`, `route` or `preset`) is wrong. The `message` names the field. See [What a mistake returns](https://pirouter.ai/docs/guides/routing.md#what-a-mistake-returns).

Fix the request as the `message` says. Retrying the same request does not help.

### `rate_limited`

Requests are arriving faster than the rate limit allows. The response carries a `Retry-After` header with a number of seconds, from 1 to 60.

Wait that long, then retry with fewer requests in flight. The limit belongs to the deployment the request was routed to, not to your key, so switching keys does not help. How the limits work is in [Rate limits](https://pirouter.ai/docs/api/rate-limits.md).

### `internal_error`

Something failed on the pirouter side. Retry after a short wait. If it keeps happening, report it with the `X-Pi-Trace-Id` of a failed request.

### `provider_unavailable`

No healthy provider can serve the model at the moment. Retry after a short wait. To keep getting replies while one model is down, name [fallback models](https://pirouter.ai/docs/guides/fallbacks.md) on the request.

The same `503` and code are also returned when the provider refused pirouter's own credential (the provider returned `401` or `403`, not a problem with your key) and no other provider could take over. That response carries `X-Pi-Provider` and `X-Pi-Model`, naming the provider that refused.

## Errors without a code

These three statuses carry no `error.code`. Branch on the HTTP status for them.

| HTTP status | Meaning                                                                                                                                                                                              | What to do                                                  |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `502`       | The gateway could not complete the request with the provider                                                                                                                                         | Retry after a short wait                                    |
| `413`       | The request body is larger than the endpoint accepts: 32 MiB for [image edits](https://pirouter.ai/docs/api/image-edits.md). `error.type` is `invalid_request_error`. Nothing is forwarded or billed | Send smaller files; retrying the same request does not help |
| `503`       | The gateway could not check the request against your key and balance. `error.type` is `service_unavailable`                                                                                          | Retry after a short wait                                    |

> **Warning:** Do not rely on the body of a `502` being JSON. Check the status first, then try to parse the body.

## Errors from the provider

pirouter passes on an error the provider itself returned, with the provider's own status and body. A provider's error is not rewritten into the pirouter error body, so its body follows the protocol of the endpoint you called. Only a response a provider returned carries the `X-Pi-Provider` header; an error raised by the pirouter gateway never does, which is how you tell the two apart. The exception is a [long request](https://pirouter.ai/docs/guides/timeouts.md#long-requests), which carries no provider headers either way.

One case is not passed on: when the provider refused pirouter's own credential, you get the pirouter error body with `503` and [`provider_unavailable`](https://pirouter.ai/docs/api/errors.md#provider_unavailable) instead of the provider's status and body. It still carries `X-Pi-Provider` and `X-Pi-Model`, but not `X-Pi-Upstream-Model` or `X-Pi-Upstream-Request-Id`, and none of the provider's own headers.

Before you see a provider's `429` or `5xx`, pirouter has already failed over to the model's other providers. See [Failover](https://pirouter.ai/docs/guides/routing.md#failover).

## Timeouts and long requests

A provider that has not started answering after 600 seconds ends the request with `upstream_timeout`. A request whose provider is silent for more than 90 seconds starts with `200`, and any later error arrives in the body or the stream. For the limits, the keepalives and how to set client timeouts, see [Timeouts and long requests](https://pirouter.ai/docs/guides/timeouts.md).

## What to retry

| HTTP status                                                              | Retry?                                                                                                            |
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| `400`, `401`, `404`, `413`, `422`                                        | No. The same request returns the same error. Fix the request or the key                                           |
| `402`                                                                    | Only after you add credit                                                                                         |
| `429`                                                                    | Yes, after the number of seconds in `Retry-After`. See [Rate limits](https://pirouter.ai/docs/api/rate-limits.md) |
| `500`, `502`, `503`, `504`                                               | Yes, with a growing wait between attempts                                                                         |
| `200` with an `error` key in the body, or an `error` event in the stream | Read `error.code` and treat it like the matching status. `upstream_timeout` can be retried                        |

pirouter fails over to another provider by itself before it returns an error, so a retry of your own is a second line of defense. Space your attempts seconds apart, not milliseconds.

## Next steps

- [Provider routing](https://pirouter.ai/docs/guides/routing.md): How a request picks its provider, and how it fails over to another one.
- [Model fallbacks](https://pirouter.ai/docs/guides/fallbacks.md): Switch to the next model when the one you asked for is unavailable.
