# Errors

> Every error is JSON with a code and a message; no credits are charged for a failed request, with one documented 503 exception.

An error response has a JSON body:

```json
{
  "error": {
    "code": "insufficient_credits",
    "message": "Account has 5 credits available, this request requires at least 30 reserved.",
    "details": { "balance": 5, "required_reserve": 30 }
  }
}
```

`code` is stable and meant for programs; `message` is for people and may change. `details` appears only for some codes.

| Status | Code                   | Meaning                                                                                                                                        | What to do                                                                                      |
| ------ | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| 400    | `invalid_request`      | Missing `file`, wrong `Content-Type`, or an invalid field value; `details.field` names it (and `details.allowed` lists valid `ocr_model` ids). | Fix the request.                                                                                |
| 401    | `invalid_api_key`      | Missing, malformed, revoked or unknown key, or a suspended account.                                                                            | Check the key ([Authentication](https://md.tlelabs.com/docs/authentication/)).                  |
| 402    | `insufficient_credits` | Not enough available credits for the reservation; `details` has `balance` and `required_reserve`.                                              | Add credits, or wait for conversions in progress to finish.                                     |
| 404    | `not_found`            | No such endpoint.                                                                                                                              | Check the path.                                                                                 |
| 405    | `method_not_allowed`   | Wrong HTTP method for the endpoint.                                                                                                            | Check the method.                                                                               |
| 413    | `file_too_large`       | File over 50 MB, a PDF over 20 MB that needs OCR, or a part inside the file too large.                                                         | Split or shrink the file.                                                                       |
| 415    | `unsupported_format`   | Not HTML, DOCX, PDF, PPTX or XLSX.                                                                                                             | Convert the file to a supported format first.                                                   |
| 422    | `page_limit_exceeded`  | Over 500 PDF pages, 200 slides or 50 sheets.                                                                                                   | Split the document.                                                                             |
| 422    | `image_limit_exceeded` | More than 50 pictures to OCR in an HTML, DOCX, PPTX or XLSX file.                                                                              | Split the file or send `ocr=off`.                                                               |
| 422    | `parse_failed`         | The file is damaged or password-protected, has no slides, or contains no text that could be extracted.                                         | Check the file; do not retry unchanged.                                                         |
| 429    | `too_many_concurrent`  | Too many conversions in progress for this key.                                                                                                 | Retry after the `Retry-After` header ([Rate limits](https://md.tlelabs.com/docs/rate-limits/)). |
| 500    | `internal_error`       | Unexpected failure.                                                                                                                            | Retry later.                                                                                    |
| 502    | `ocr_provider_error`   | The document has no text left because OCR failed or was unavailable.                                                                           | Retry later, or choose another `ocr_model`.                                                     |
| 503    | `service_unavailable`  | Temporary failure.                                                                                                                             | Retry with backoff; see below when `details.request_id` is present.                             |

## Charging on errors

No credits are charged for a request that returns an error: the reservation is released immediately.

The one exception is a `503 service_unavailable` with `details.request_id`. It means the charge could not be confirmed and may have been recorded. Before retrying, look for that `request_id` in [`GET /v1/wallet/transactions`](https://md.tlelabs.com/docs/wallet/#get-v1wallettransactions): if a `convert` transaction has it, the conversion was charged (the Markdown is lost; converting again charges again).

A response that is not JSON (for example an HTML error page from the network edge) should be treated like a `5xx` and retried later. A reservation left by an interrupted request expires after 5 minutes and is then released automatically.
