# Errors and limits

> The error envelope, every error code and what to do about it, request ids, rate limits, and retrying safely.

Every error is JSON in one envelope, whatever the endpoint:

```json title="Response — 422"
{
  "error": {
    "code": "validation_failed",
    "message": "The request failed validation.",
    "request_id": "8f3c2a1b9d4e5f60",
    "issues": [
      {
        "location": "body",
        "field": "text",
        "message": "Too small: expected string to have >=1 characters"
      }
    ]
  }
}
```

| Field | What it is |
|---|---|
| `code` | what went wrong, from the table below — switch on this, not on `message` |
| `message` | a sentence meant for a person; its wording may change |
| `request_id` | the request's id, the same as the `X-Request-Id` header |
| `issues` | where a request failed validation, one entry per problem; empty for every other error |

The [agent's WebSocket](https://docs.lumavox.ai/agents/websocket.md) sends the same `code` and `message` in its `error` frames.

## Codes

| Status | `code` | Means | What to do |
|---|---|---|---|
| `400` | `invalid_request` | the request could not be read — the body is not valid JSON, or not the form the endpoint takes — or cannot be served as a whole | fix the request; the message says what was wrong |
| `401` | `unauthenticated` | no credential, or one that is invalid, revoked or expired | check the key |
| `402` | `insufficient_credits` | your organisation is out of credit, or the balance cannot cover what you asked to set aside or what a queued job's estimate would charge | see [credits and usage](https://docs.lumavox.ai/usage.md) |
| `403` | `forbidden` | the credential is valid but not for this endpoint — usually a missing scope — or your organisation was deleted | use a key with the scope the message names; see [authentication](https://docs.lumavox.ai/authentication.md) |
| `404` | `not_found` | no such id — or one that belongs to another organisation, which answers the same — or no such path | check the id and the URL |
| `405` | `method_not_allowed` | the path exists, but not with this method; the `Allow` header lists the methods it takes | use one of them |
| `409` | `conflict` | a plan's limit on how many agents, voices or end users you hold, an edit to something that changed since you read it, or something still being processed | read the message: delete something, re-read and retry, or wait |
| `413` | `too_large` | an upload or body over its size limit | see the endpoint's reference |
| `415` | `invalid_request` | an upload of a type the endpoint does not read | see the endpoint's reference |
| `422` | `validation_failed` | the request does not match the schema, or a value is out of range; `issues` names each field | fix the fields `issues` names |
| `429` | `rate_limited` | a rate limit — see below | wait `Retry-After` seconds, then retry |
| `500` | `internal` | something failed on our side | retry later; report the `request_id` if it persists |
| `502` | `provider_failed` | a provider the request depends on failed | retry; report the `request_id` if it persists |
| `503` | `unavailable` | something is briefly unreachable, or a capability is not set up on this deployment | retry shortly; the message says which |

A request is authenticated before it is validated: a request with no credential and a bad body is a `401`, never a `422`. A new code may be added: treat one you do not know by its status.

## Validation errors

A `422` has one entry in `issues` per problem:

| Field | What it is |
|---|---|
| `location` | where the value was: `body`, `query`, `path` or `header` |
| `field` | the field, dotted for a nested one (`references.0.asset_id`); `null` when the problem is the whole body |
| `message` | what is wrong with it |

When the schema itself refuses the request, `message` is `The request failed validation.` and every problem is in `issues`. When the endpoint refuses a value it read — an unknown `end_user_id`, a limit above your plan's — `message` says why, and its one issue says where.

A body may only hold the fields its endpoint knows: an unknown one is an issue of its own, naming it.

## Request ids

Every response — success or error — carries an `X-Request-Id` header, and every error repeats it as `request_id`. Quote it when you report a problem: it finds your request in our logs.

You can send your own:

```bash title="curl"
curl https://api.lumavox.ai/v1/tts \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Request-Id: order-1234-greeting" \
  -H "Content-Type: application/json" \
  -d '{"text": "Your order is on its way."}' \
  --output greeting.wav
```

An id of 1 to 64 letters, digits, `.`, `_` and `-`, starting with a letter or digit, is used as given. Anything else is replaced with a fresh id rather than refused.

## Rate limits

Limits belong to your organisation's plan and are shared by every key in it. They count the requests that spend — speech, transcription, generation submits, uploads, voice design, knowledge writes. Reads and polls are not counted.

A plan can limit:

- requests a minute and requests an hour;
- requests running at the same time — the ones that keep working while you wait: speech and transcription (`/v1/tts`, `/v1/stt`), uploading a voice, designing one and keeping a design, and confirming an upload. A queued submit answers at once, so it holds nothing;
- each of those again for one capability — speech, images, transcription and so on — on top of the organisation-wide ones.

Over any of them, the request answers **429** with a `Retry-After` header in seconds. Nothing has been charged. The message says which limit it was:

```json title="Response — 429"
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded: more than 300 requests per minute.",
    "request_id": "8f3c2a1b9d4e5f60",
    "issues": []
  }
}
```

When your plan sets no window a minute, the platform's own applies. Some capabilities have limits of their own, also answered with 429: how many of your generations may be unfinished at once, and how many ephemeral keys you mint a minute. Plans also cap how many agents, voices and end users you hold; past those, creating another is a **409**. The platform's limits, as its staff set them, are listed on [Limits](https://docs.lumavox.ai/limits.md).

## Retrying safely

- **Reads and polls** are safe to retry at any time.
- **`429`**: wait the `Retry-After` seconds, then retry.
- **`500`, `502`, `503`**: retry with an increasing delay — a second, then a few, then longer.
- **Everything else in the 4xx range** will fail the same way again. Fix the request first.

The API has no idempotency keys: **a retried `POST` is a new request**. If a submit to a queued endpoint timed out on your side, it may have been accepted — and charged — anyway. Before you send it again, look for it with `GET /v1/generations?status=queued` (or `running`); see [queued jobs](https://docs.lumavox.ai/jobs.md). The one create that is safe to repeat is `POST /v1/end-users` with your own `external_id`, which returns the same end user every time.

## Related

- [Conventions](https://docs.lumavox.ai/conventions.md) — how times, credits and lists are written.
- [Authentication](https://docs.lumavox.ai/authentication.md) — keys, scopes, 401 and 403.
- [Credits and usage](https://docs.lumavox.ai/usage.md) — the balance, and what 402 means.
- [Queued jobs](https://docs.lumavox.ai/jobs.md) — reconciling submits after a failure.
