# Errors and quota

The stable error codes, what each one means, and how the monthly quota is counted.

Page: https://legivel.com/docs/errors

This page is the code reference: what each refusal means and what to do about it. The numbers behind the refusals are on [Limits](https://legivel.com/docs/limits): what each plan allows, how a document is measured and what a refusal costs.

## Error format

Every error is a JSON body with at least two fields. `code` is the stable contract: it never changes meaning, so script against it. `error` is a sentence for people and may be reworded at any time. A refusal against a limit (413 `payload_too_large`, 422 `too_many_resources`, 422 `too_many_pages`, 429 `rate_limited` and 422 `render_timeout`) adds `limit`: the limit's name, the value that was exceeded and the plan it came from, so a client reads the number without parsing the sentence. The names are the plan columns [Limits](https://legivel.com/docs/limits) lists. One refusal in that set carries no `limit`: a 413 with `reason` `preparation_limit`, where the renderer, not the plan, set the bound. Treat `limit` as optional and read `reason` when it is missing. A failed remote resource adds `reason` and `resource_id`. When the server knows how long to wait, a `Retry-After` header gives the number of seconds. Only the codes the table marks send it.

The three shapes, as the document publishes them:

HTTP/1.1 400 · empty_document

```json
{
  "code": "empty_document",
  "error": "The request carried no Markdown to render."
}
```

HTTP/1.1 413 · payload_too_large

```json
{
  "code": "payload_too_large",
  "error": "The document, one of its resources or the request as a whole is over a size limit.",
  "reason": "markdown_limit",
  "limit": {
    "name": "markdown_kb",
    "value": 256,
    "plan": "free"
  }
}
```

HTTP/1.1 400 · invalid_request

```json
{
  "code": "invalid_request",
  "error": "A field of the request was missing, of the wrong type or out of range.",
  "reason": "render_options"
}
```

401 responses also carry `WWW-Authenticate: Bearer`. All API responses, errors included, are sent with `Cache-Control: no-store`.

## Error codes

The codes below are every value the documented routes can produce, with the fields the envelope carries beside `code` and `error`. The sentences are the current wording and may change.

| Status | Code | Meaning | What to do | Also carries | Retry-After |
| --- | --- | --- | --- | --- | --- |
| 401 | `unauthorized` | The request carried no API key, or one that is no longer valid. | Check the `Authorization` header and that the key has not been revoked. | None | No |
| 403 | `account_blocked` | The account is blocked and cannot render or download until support lifts it. | Contact support; retrying answers the same. | None | No |
| 403 | `email_unverified` | The account's email address has not been verified yet. | Open the link in the sign-up mail or request a new one, then call again. | None | No |
| 400 | `invalid_request` | A field of the request was missing, of the wrong type or out of range. | Fix the field `reason` names and send the request again. | `reason` | No |
| 413 | `payload_too_large` | The document, one of its resources or the request as a whole is over a size limit. | Send a smaller document, and on `preparation_limit` make it simpler rather than changing plan. | `limit`, `reason`, `renderer_bound` | No |
| 400 | `empty_document` | The request carried no Markdown to render. | Send a document with content in it. | None | No |
| 422 | `too_many_pages` | The rendered document has more pages than were allowed for it: the plan's page limit, or the deployment's ceiling where that is lower. | Split the document. `limit.plan` names the plan whose limit it was, and a plan with a higher one lifts it; `limit.plan` null means the deployment capped it below the plan, and only the operator can raise that. | `limit` | No |
| 422 | `too_many_resources` | The document references more resources than one render loads. | Reference fewer resources, or split the document into several renders. | `limit` | No |
| 422 / 503 | `resource_fetch_failed` | A resource the document references could not be loaded. | Check that the resource `resource_id` names is reachable, then retry. | `reason`, `resource_id` | No |
| 409 | `idempotency_in_progress` | A render with the same Idempotency-Key is still running. | Wait the `Retry-After` seconds and retry with the same key. | None | Yes |
| 422 | `idempotency_mismatch` | The Idempotency-Key was already used for different document or resource content. | Use one `Idempotency-Key` per document; retrying unchanged answers the same. | None | No |
| 429 | `idempotency_replay_limit` | The Idempotency-Key has been replayed as often as the plan allows. | Render under a new key, which uses one of the period's documents, or wait for the key to expire. | None | Yes |
| 429 | `rate_limited` | The account sent more requests, or asked for more concurrent renders, than its plan allows. | Wait the `Retry-After` seconds and retry, letting the running renders finish first when `limit` names concurrency. | `limit` | Yes |
| 402 | `quota_exhausted` | The account has no documents left for the current period. | Check `GET /v1/usage`, then upgrade the plan or wait for the next period. | None | No |
| 503 | `quota_unavailable` | The account's usage could not be checked, so no render was started. | Retry after a short pause; nothing was counted. | None | No |
| 503 | `queue_full` | The render queue is full; the same request can be sent again shortly. | Retry after the `Retry-After` delay; the document was returned to your period. | None | Yes |
| 422 | `render_timeout` | The render ran past the time allowed for it: the plan's time limit, or the deployment's ceiling where that is lower. | Simplify or split the document; retrying unchanged answers the same. `limit.plan` null means the deployment capped the time below the plan, and only the operator can raise that. | `limit` | No |
| 503 | `render_unavailable` | No render capacity was available for this request, or this deployment does not offer what it asked for, such as upload delivery. | Retry after a pause, and report it if it persists. | None | No |
| 422 | `render_failed` | The document could not be rendered. | Retry once; if it fails again the document is at fault. | None | No |
| 403 | `plan_required` | The account's plan does not include what the request asked for. | Move to a plan that includes what `reason` names. | `reason` | No |
| 400 | `invalid_bundle` | The certificate bundle in the request could not be read. | Check the bundle's format against what `reason` says and send it again. | `reason` | No |
| 400 | `certificate_expired` | The certificate is past its validity period. | Seal with a certificate that is still inside its validity period. | `reason` | No |
| 422 | `signing_failed` | The document was rendered but could not be sealed. | Retry once, and check the certificate if it fails again. | None | No |
| 503 | `signing_unavailable` | Sealing is temporarily unavailable. | Retry after a pause; `reason` says what was unavailable. | `reason` | No |
| 400 | `unknown_signing_identity` | No stored certificate of this account matches the one the request named. | Name one of the account's stored certificates, or send the bundle in the request. | None | No |
| 404 | `not_found` | The usage period asked for does not belong to this account, or the path does not exist. | Check the path and, on `GET /v1/usage`, that `period` is one of this account's periods. | None | No |
| 405 | `method_not_allowed` | The path exists but does not answer this HTTP method; the Allow header lists the ones it does. | Use a method the `Allow` header names. | None | No |
| 424 | `upload_failed` | The storage at `upload_url` did not accept the PDF or did not answer in time; `status` is its HTTP status, or null when no answer arrived. | Check that the pre-signed URL is still valid and signed for `Content-Type: application/pdf`, then retry with a fresh one; the document was returned to your period. | `status` | No |
| 500 | `internal_error` | The request failed for a reason the service could not classify. | Retry after a pause, and report it if it persists. | None | No |

## Monthly quota

Each plan includes a number of documents per period; the counts are on [Limits](https://legivel.com/docs/limits#plans). Periods start each month at 00:00 UTC on the day the account signed up or, after a purchase, on the day of the purchase. A monthly upgrade starts a new period on its day. The counter is transactional in the database, so it is exact under concurrency. It counts only PDFs that were actually served: a document is reserved before the render starts and returned to your period when the render fails, times out, exceeds the page limit, or cannot be dispatched. Which refusals return the document to your period is on [Limits](https://legivel.com/docs/limits#refusals).

When the period's documents are used up, `/v1/render` answers 402 `quota_exhausted`; the message says the period's documents are used up and links to [pricing](https://legivel.com/pricing). Grants add documents to the period. Check `credits_used`, `credits_included`, `credits_granted` and the period boundaries with [`GET /v1/usage`](https://legivel.com/docs/api/usage).

When to retry

Retry 429 and 503 after the `Retry-After` delay (or a short pause when the header is absent). Do not retry 400, 402 or 422 without changing the request: the same input gives the same answer.
