
# Errors reference

Every error the inference API returns uses one envelope:

```json
{
  "error": {
    "type": "invalid_request_error",
    "message": "human-readable explanation",
    "code": "machine-readable code",
    "param": "the offending field, when there is one"
  }
}
```

`type` is one of a small fixed set (`authentication_error`,
`invalid_request_error`, `not_found`, `provider_error`,
`provider_unavailable`, `internal_server_error`).
`code` is more specific — branch on it in code, show `message` to
humans. The messages below are the real strings the gateway produces,
not paraphrases.

## 401 — authentication

You'll see one of:

```json
{
  "error": {
    "type": "authentication_error",
    "message": "Missing Authorization header",
    "code": "authentication_error"
  }
}
```

```json
{
  "error": {
    "type": "authentication_error",
    "message": "API key not registered. Check the key was copied in full, or create a new one at /dashboard/keys. If it was working before, it may have been revoked.",
    "code": "api_key_not_registered"
  }
}
```

A key whose *shape* rules it out gets a different code, because the fix
is different — and we can tell without looking it up:

```json
{
  "error": {
    "type": "authentication_error",
    "message": "API key is malformed: the key body is 20 characters, expected 43 — this is usually a copy-paste truncation. Re-copy the full token, or create a new key at /dashboard/keys.",
    "code": "api_key_malformed"
  }
}
```

`api_key_malformed` covers a truncated or over-copied key (the body is
not exactly 43 characters), a token that doesn't start with `sk-lr-`,
and the retired `sk-bf-*` scheme:

```json
{
  "error": {
    "type": "authentication_error",
    "message": "API key is malformed: this is a retired sk-bf-* key and the scheme is no longer accepted. Create a new key at /dashboard/keys.",
    "code": "api_key_malformed"
  }
}
```

```json
{
  "error": {
    "type": "authentication_error",
    "message": "API key has expired. Create a new key in the dashboard.",
    "code": "api_key_expired"
  }
}
```

**Fix:** send `Authorization: Bearer sk-lr-...` with a key from
[the dashboard](../getting-started/api-keys). `api_key_malformed` means
the token can't be one of ours as written — nearly always a copy-paste
truncation, so re-copy the whole thing. `api_key_not_registered` means
the shape is right but no account owns it: check you copied the whole
key and are using the right account, or mint a new one; a key that used
to work may have been revoked. `api_key_expired` means the key reached its
expiry date: keys have a bounded lifetime (default 90 days), so create
a fresh key in the dashboard and rotate it in — the expired one stays
listed there as a reminder but can no longer authenticate.

## 402 — insufficient credits

```json
{
  "error": {
    "type": "invalid_request_error",
    "message": "Insufficient credits. Balance: $0.0000. Your balance must be above zero to make requests. Please add credits to your account.",
    "code": "insufficient_credits"
  }
}
```

The balance is checked before the request is forwarded, so a 402 never
costs you tokens. Any positive balance admits a request; because usage
is settled after the response, a single request can take the balance
below zero, and further requests 402 until you top up.

**Fix:** top up in [Credits & billing](credits-and-billing).

## 400 — malformed request

Missing or empty `messages`:

```json
{
  "error": {
    "type": "invalid_request_error",
    "message": "messages field is required and cannot be empty",
    "code": "invalid_request_error",
    "param": "messages"
  }
}
```

A model id that doesn't match the public shape
(`{provider}/{creator}/{model}[/{locode}]` — see
[available models](../models/available)):

```json
{
  "error": {
    "type": "invalid_request_error",
    "message": "model id must be {provider}/{creator}/{model}[/{locode}]: got 5 segments",
    "code": "invalid_request_error",
    "param": "model"
  }
}
```

An alias name that violates the naming rule:

```json
{
  "error": {
    "type": "invalid_request_error",
    "message": "alias name must be 1-64 chars: lowercase a-z, 0-9, '-' or '_', starting with a letter or digit (got \"My Alias\")",
    "code": "invalid_alias_name",
    "param": "model"
  }
}
```

**Fix:** the message names the field; `param` confirms it. Compare
against the request shape in
[your first completion](../getting-started/first-completion).

## 404 — the route doesn't exist

Unknown model on a provider:

```json
{
  "error": {
    "type": "not_found",
    "message": "model \"anthropic/claude-opus-4.6\" is not offered by provider \"openai\"",
    "code": "not_found",
    "param": "model"
  }
}
```

A model that is only served in specific regions, addressed with an
explicit `/global` it doesn't have. (Omitting the region entirely does
**not** produce this error — it resolves to the model's default region;
see [how IDs resolve](../models/routing#how-ids-resolve). You only see
this when you pin `global` explicitly.)

```json
{
  "error": {
    "type": "not_found",
    "message": "model \"anthropic/claude-opus-4.6\" on provider \"vertex\" is served only in specific regions and has no global endpoint. Append a locode (e.g. \"vertex/anthropic/claude-opus-4.6/{locode}\") and check /providers/vertex for available regions, or omit the region to route to the model's default one",
    "code": "not_found",
    "param": "model"
  }
}
```

An alias that doesn't exist on your account:

```json
{
  "error": {
    "type": "not_found",
    "message": "alias \"alias/big\" does not exist on this account — create or repoint aliases in the dashboard at /dashboard/aliases",
    "code": "alias_not_found",
    "param": "model"
  }
}
```

**Fix:** browse the [model catalog](../models/available) or
`GET /v1/models` for exact ids; aliases are per-account, so a key from
another account won't see yours.

## 503 — the route exists but can't serve you right now

A region the model isn't served in. This is deliberate: we never
silently substitute a different region — the region you pin is the
region you get, even when a "nearby" one would work:

```json
{
  "error": {
    "type": "provider_unavailable",
    "message": "model \"mistral/mistral-large\" is not served in region \"de-ber\" on provider \"mistral\" — check /providers/mistral for available regions",
    "code": "provider_unavailable",
    "param": "model"
  }
}
```

A model we've marked unavailable says so with
`"code": "model_unavailable"` and names the reason. When the route
itself fails upstream, the request returns 503 with
`"type": "provider_error"` naming the provider that failed. LowRouter
does not silently retry on a different provider — an explicit ID is a
pin and an auto-routed request commits to the winner of its ranking —
so the provider you see is the provider you asked for, directly or by
delegation. `lowrouter_metadata.providers_attempted` shows what was
tried.

**Fix:** pick a served region from `/providers/{provider}`, or point
an [alias](../models/aliases) at an alternative route so clients don't
need redeploying when a region is down.

### Daily quota exhausted

When an upstream provider has spent its daily token quota for a route,
retrying is pointless until the quota resets — so the gateway tells you
exactly that, with `"code": "provider_quota_exhausted"` and a
`Retry-After` header carrying the seconds until the reset (midnight
UTC):

```json
{
  "error": {
    "type": "provider_unavailable",
    "message": "Provider aws-bedrock has exhausted its daily token quota for bedrock/anthropic.claude-3-5-sonnet-20240620-v1:0 in jp-tyo. The quota resets at midnight UTC; retry after the Retry-After interval.",
    "code": "provider_quota_exhausted",
    "param": "model"
  }
}
```

The region in the message is the locode the route is billed and reported
under — the same one that appears as the fourth segment of a model ID —
not the upstream provider's own region name.

While the quota is exhausted the affected region also disappears from
`/v1/models`, so a fresh listing won't hand you an ID that can't serve.
Your pin is still honoured — nothing is substituted.

**Fix:** honour `Retry-After` instead of hammering the route, or point
the request (or an [alias](../models/aliases)) at another region or
provider serving the same model.

## 429 — rate limited

The only `429` the gateway itself emits is its per-IP limiter, with a
`Retry-After` header:

```json
{
  "error": {
    "type": "invalid_request_error",
    "message": "Rate limit exceeded. Too many requests from your IP address. Try again in 30 seconds",
    "code": "rate_limit_exceeded"
  }
}
```

An *upstream provider's* rate limit is not passed through as a 429:
it surfaces as one of the `503` provider failures above.

**Fix:** honour `Retry-After` and back off with jitter — or route the
workload across more than one provider, which is rather the point of
a router.

## What you'll never get

No silent substitutions: a failed constraint (region, jurisdiction,
alias) is an error, not a quiet reroute to somewhere you didn't ask
for. If you got a 2xx, the response's `lowrouter_metadata` tells you
exactly who served it and where — see
[per-request metadata](../models/per-request-metadata).
