---
title: "Errors"
description: "Every error the pirouter API returns: the error body, each error code with its HTTP status and what to do about it, timeouts, and which errors to retry."
canonical: "https://pirouter.ai/docs/errors"
last-updated: "2026-10-13"
lang: "en"
source: "content/docs/en/(start)/errors.mdx"
---

# Errors

Every error the pirouter API returns: the error body, each error code with its HTTP status and what to do about it, timeouts, and which errors to retry.

When a request to pirouter fails, the HTTP status says what kind of failure it was, and `error.code` in the body says exactly which one. This page lists every code and what to do about each.

## The error body

The inference endpoints of all three protocols answer 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 answer in a shorter shape, shown in the [API overview](https://pirouter.ai/docs/api.md#errors).

## Error codes

| HTTP status | `error.code`                                                                      | Meaning                                                                              |
| ----------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `400`       | [`invalid_request`](https://pirouter.ai/docs/errors.md#invalid_request)           | A required field is missing from the request body.                                   |
| `401`       | [`invalid_api_key`](https://pirouter.ai/docs/errors.md#invalid_api_key)           | No key was sent, or the value is not a pirouter key.                                 |
| `401`       | [`api_key_not_found`](https://pirouter.ai/docs/errors.md#api_key_not_found)       | The key has the right format but no such key exists.                                 |
| `401`       | [`api_key_revoked`](https://pirouter.ai/docs/errors.md#api_key_revoked)           | The key was revoked.                                                                 |
| `401`       | [`api_key_expired`](https://pirouter.ai/docs/errors.md#api_key_expired)           | The key is past its expiration.                                                      |
| `402`       | [`insufficient_balance`](https://pirouter.ai/docs/errors.md#insufficient_balance) | The wallet balance cannot cover the request.                                         |
| `404`       | [`model_not_found`](https://pirouter.ai/docs/errors.md#model_not_found)           | The model ID is unknown or not enabled.                                              |
| `404`       | [`no_eligible_provider`](https://pirouter.ai/docs/errors.md#no_eligible_provider) | The routing settings exclude every provider of the model.                            |
| `422`       | [`validation_failed`](https://pirouter.ai/docs/errors.md#validation_failed)       | The request breaks a rule: the wrong endpoint for the model, or a bad routing field. |
| `429`       | [`rate_limited`](https://pirouter.ai/docs/errors.md#rate_limited)                 | Requests are arriving faster than the rate limit allows.                             |
| `500`       | [`internal_error`](https://pirouter.ai/docs/errors.md#internal_error)             | An unexpected failure on the pirouter side.                                          |
| `503`       | [`provider_unavailable`](https://pirouter.ai/docs/errors.md#provider_unavailable) | No healthy provider can serve the model right now.                                   |

### `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`. Add the field and send the request again.

### `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.

### `api_key_not_found`

The value looks like a pirouter key, but no such key exists. Check that the whole key was copied.

### `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/billing.md#add-credit), then send the request again. Do not retry automatically: the answer stays `402` until the balance changes. [When the balance runs out](https://pirouter.ai/docs/billing.md#when-the-balance-runs-out) has the details.

### `model_not_found`

The model ID is unknown or not enabled. Model IDs are written as `vendor/model`, so a name copied from a provider'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).

### `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/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/routing.md#what-a-mistake-returns).

### `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.

### `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 answering while one model is down, name [fallback models](https://pirouter.ai/docs/fallbacks.md) on the request.

## Errors without a code

Three errors have no `error.code`, because the gateway did not reach a decision about the request. Branch on the HTTP status for these.

| HTTP status | When                                                                                                         | What to do                    |
| ----------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------- |
| `502`       | The gateway could not complete the request with the provider                                                 | Retry after a short wait      |
| `503`       | The gateway could not check the request against your key and balance. `error.type` is `service_unavailable`  | Retry after a short wait      |
| `504`       | The provider sent nothing within the time limit. See [Timeouts](https://pirouter.ai/docs/errors.md#timeouts) | Retry, or stream the response |

Do not rely on the body of a `502` or `504` being JSON.

## Errors from the provider

pirouter passes on an error that the provider itself answered, 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 that a provider answered carries the `X-Pi-Provider` header; an error raised by the pirouter gateway never does, which is how you tell the two apart.

Before you see a provider's `429` or `5xx`, pirouter has already tried the other providers of the model. See [Automatic retry on another provider](https://pirouter.ai/docs/routing.md#automatic-retry-on-another-provider).

## Timeouts

pirouter waits up to 95 seconds for the provider to start answering. If nothing arrives in that time, the request ends with `504`.

The 95-second limit applies to the first byte of the response, not to the whole response:

- A streamed response has no limit on its total duration once it has started.
- A response that is not streamed arrives in one piece, so the whole answer has to be ready within 95 seconds. For long outputs and for models that reason before they answer, stream the response.

Set your client's timeout above 95 seconds. A shorter one gives up on requests that the gateway would still have answered.

## What to retry

| HTTP status                | Retry?                                                                  |
| -------------------------- | ----------------------------------------------------------------------- |
| `400`, `401`, `404`, `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`                       |
| `500`, `502`, `503`, `504` | Yes, with a growing wait between attempts                               |

pirouter retries on 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/routing.md): How a request picks its provider, and how it retries on another one.
- [Model fallbacks](https://pirouter.ai/docs/fallbacks.md): Switch to another model when the one you asked for is unavailable.
