# Operations and limits

[Get started](/api/getting-started.html) · [API reference](/api/docs) · [Understand output](/api/understanding-output.html) · [Webhooks](/api/webhooks.html) · [Versions and changelog](/api/versioning.html)

## Processing lifecycle

An upload returns as soon as the file is accepted; processing then runs in the background. `GET /documents/{id}/processing/latest` reports one of four statuses:

| `status`      | Meaning                                                          |
| ------------- | ---------------------------------------------------------------- |
| `in_progress` | Queued or processing.                                            |
| `completed`   | Every page is processed and results are ready.                   |
| `failed`      | Processing failed. `error_code` and `error_message` explain why. |
| `unknown`     | The run was canceled and won't continue.                         |

A canceled run sends no webhook event. The reconciliation sweep under Recover a missed event picks it up; upload the file again, as a new operation, if you still need results.

Document listings report the same state in `processing_status`, using uppercase values:

| `processing_status`                           | `/processing/latest` status |
| --------------------------------------------- | --------------------------- |
| `CREATED`, `PENDING`, `QUEUED`, `IN_PROGRESS` | `in_progress`               |
| `SUCCESS`                                     | `completed`                 |
| `FAILED`, `TIMEOUT`                           | `failed`                    |
| `CANCELED` and any other value                | `unknown`                   |

The progress fields `pages_done`, `pages_total`, `total_known` and `stage` are for display; the reference describes each one. Use `status` to decide when a document is finished.

While a document is processing, `/parse` returns the pages completed so far. Treat those as partial results until `status` is `completed`.

To retry a failed document, check its `error_code` first: limit and billing codes need a fix before a retry can succeed. Then upload the file again with a new `Idempotency-Key`.

## Errors

Every error response uses [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem details (`application/problem+json`) with `type`, `title`, `status` and `detail`, plus a `code` where one is listed below. Branch on `status` and `code`, not on `detail`. Every response carries an `X-Request-Id` header. You can send your own UUID v4 in `X-Request-Id` to correlate requests; include the ID when you contact support.

| Response                                                                                                                   | What to do                                                                                                                                        |
| -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| **400** Invalid request, parameter, or `X-API-Version`.                                                                    | Fix the request and send it with a new `Idempotency-Key`.                                                                                         |
| **400** Invalid OCR file (`detail` says why), `ocr` and `ocr_format` not sent together, or OCR with a non-PDF.             | Fix the OCR file or fields, then upload again with a new `Idempotency-Key`. No document was created.                                              |
| **401** Missing, invalid, expired or revoked API key.                                                                      | Check the API key in Studio. Don't retry with the same API key.                                                                                   |
| **402** `PAYG_INSUFFICIENT_FUNDS`: Prepaid balance too low. No document was created.                                       | Add funds in Studio, then upload again.                                                                                                           |
| **403** `PAYG_PAYMENT_PAUSED`, `PAYG_WALLET_FROZEN`: Prepaid processing is paused. No document was created.                | `PAYG_PAYMENT_PAUSED`: update the payment method in Studio. `PAYG_WALLET_FROZEN`: contact support.                                                |
| **403** The API key can't use this endpoint, the trial limit is reached, or Enterprise billing isn't accepting uploads.    | Replace an API key that can't use the endpoint with a new key from Studio; review billing in Studio.                                              |
| **403** OCR import isn't enabled for this account (an upload with an `ocr` part). No document was created.                 | Ask support to enable it, or upload without `ocr` and `ocr_format`.                                                                               |
| **404** Not found, owned by another account, or deleted.                                                                   | Check the ID and the account.                                                                                                                     |
| **404** `PROCESSING_NOT_FOUND`: The document has no processing run yet.                                                    | Retry with backoff. If it persists, contact support with the document ID and `X-Request-Id` before uploading another copy.                        |
| **405** Method not supported on this path.                                                                                 | Use the documented method.                                                                                                                        |
| **409** `PAYG_BILLING_UNAVAILABLE`, `PAYG_MIGRATION_PENDING`: Billing is temporarily unavailable. No document was created. | Upload again in a few minutes.                                                                                                                    |
| **409** `WORKSPACE_CONTEXT_CHANGED`: The account's workspace changed during the upload.                                    | Upload again straight away, as a new operation. The document from the failed request isn't processed or charged, and it's deleted after 24 hours. |
| **409** The request conflicts with the document's current state.                                                           | Read the document again, then retry.                                                                                                              |
| **413** File, OCR file or request body too large.                                                                          | Reduce the size; gzip a large OCR file; see Limits below.                                                                                         |
| **415** Unsupported file type.                                                                                             | Upload PDF, DOC, DOCX, ODT, RTF, EML or MSG.                                                                                                      |
| **422** The folder has too many files for a sub-document listing.                                                          | List a smaller folder.                                                                                                                            |
| **429** Rate or concurrency limit reached.                                                                                 | Wait `Retry-After` seconds when present; otherwise back off with jitter and lower your concurrency.                                               |
| **429** `PROCESSING_PAUSED`: Processing is paused by a configured limit.                                                   | Don't retry automatically. The account owner, or for a workspace its owner, must raise the limit.                                                 |
| **429** `REQUESTS_DISABLED`: Requests are disabled by a limit set to zero.                                                 | Don't retry automatically. The account owner, or for a workspace its owner, must raise the limit.                                                 |
| **500** Unexpected error.                                                                                                  | Retry reads with backoff. Look an upload up before you upload again; see Retry safely.                                                            |
| **502** The file couldn't be stored. No document was created.                                                              | Retry the upload.                                                                                                                                 |
| **503** The service is temporarily unavailable.                                                                            | Retry reads with backoff. Look an upload up before you upload again; see Retry safely.                                                            |
| **504** The request timed out.                                                                                             | Retry reads with backoff. Look an upload up before you upload again; see Retry safely.                                                            |

Processing failures report an `error_code` on `/processing/latest` and in `document.processing.failed` events:

| Failure                                                                                                                                                                                                                                        | What to do                                                                       |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `DOCUMENT_PAGE_LIMIT_EXCEEDED`: The document has more pages than the per-document limit.                                                                                                                                                       | Split the document.                                                              |
| `CUSTOMER_PAGE_LIMIT_REACHED`: A member or API key page limit (per billing period or lifetime) is reached or set to zero.                                                                                                                      | Ask the account owner, or for a workspace its owner, to raise it.                |
| `ADMISSION_POLICY_MISSING`: The API key or account was removed before processing started.                                                                                                                                                      | Upload again with an active key.                                                 |
| `PAYG_INSUFFICIENT_FUNDS`: The prepaid balance doesn't cover the document.                                                                                                                                                                     | Add funds, then upload again.                                                    |
| `PAYG_FUNDING_UNAVAILABLE`: The balance doesn't cover the document, and processing can't wait for a top-up to arrive.                                                                                                                          | Add funds, or check automatic top-up in Studio, then upload again.               |
| `PAYG_BILLING_UNAVAILABLE`: Billing couldn't be checked at the time.                                                                                                                                                                           | Upload again in a few minutes.                                                   |
| `PAYG_MIGRATION_PENDING`: Your account's billing setup is being completed.                                                                                                                                                                     | Upload again in a few minutes.                                                   |
| `PAYG_PAYMENT_PAUSED`: Prepaid processing is paused after an automatic payment failed.                                                                                                                                                         | Update your payment method in Studio.                                            |
| `PAYG_WALLET_FROZEN`: Prepaid processing is paused by LexSelect support.                                                                                                                                                                       | Contact support.                                                                 |
| `PROCESSING_FAILED`: Any other failure, including files that can't be read (for example password-protected or damaged files), an email with more than 10 supported attachments, an exhausted Enterprise trial and inactive Enterprise billing. | Check the file and Billing in Studio, then contact support with the document ID. |

`error_message` is human-readable text for the same cause. Branch on `error_code`, not on the message.

## Retry safely

**Reads** (`GET`): retry `5xx`, `429` without a `code`, and network errors with exponential backoff and jitter, a maximum number of attempts and a deadline. Reads are never charged.

**Uploads**: treat each upload as an operation with its own `Idempotency-Key` and a unique `name` that holds no sensitive data, such as `upload-<operation ID>.pdf`, sent with `strip_file_name=false` and an explicit `parent_id`. Save the `parent_id` from your first upload response if you rely on the Default Project.

- **After `400`, `401`, `402`, `403`, `409`, `413`, `415`, `429` or `502`:** the request was answered. Fix the cause if the error names one, then upload again as a new operation, with a new `Idempotency-Key`. Some of these answers arrive after the document was created (a `403` because Enterprise billing isn't accepting uploads, a `409` with `WORKSPACE_CONTEXT_CHANGED`, a `429` with `PROCESSING_PAUSED`); that document isn't processed or charged, and it's deleted 24 hours after upload.
- **After a timeout, `500`, `503` or `504`:** the document may exist and be processing. An `Idempotency-Key` doesn't replay these responses, so look the document up before you send anything:
  1. List the parent newest first: `GET /documents?parent_id=<parent ID>&sort_by=created_at&sort_direction=desc`.
  2. Look for your `name`, or the same name with a number added before the extension, such as `upload-<operation ID> (1).pdf`; a number is added when the folder already holds that name.
  3. A new document can take a few seconds to appear, so repeat the listing for about 30 seconds.
  4. If you find it, use its `id`. If not, retry with the same `Idempotency-Key` and `name`.

**Listings**: a just-uploaded document can take a few seconds to appear in `GET /documents`. Follow `next_cursor` until it's `null`; the last page can be empty. If the item a cursor points to is deleted mid-walk, which retention makes routine for API uploads, the listing starts again from the first page. Sort by `created_at` and deduplicate by `id`.

**Pages during processing**: list `GET /documents/{id}/processing/latest/pages` after processing completes. While pages are still finishing, a cursor walk can skip or repeat pages.

## Idempotency

Send an `Idempotency-Key` header, any unique string such as a UUID, on uploads and deletes. Use one key per operation and reuse it only to retry that operation. For 24 hours, repeating a request with the same API key and the same `Idempotency-Key` returns the stored response instead of running the request again. Successful responses and `400`, `413` and `415` responses are stored; other errors aren't, so a retry with the same key runs the request again.

- A key isn't tied to an endpoint or request body, so reusing it on a different request returns the earlier response.
- Use a new key after you correct a request that failed with `400`, `413` or `415`.
- Wait for a request to finish or time out before retrying it with the same key; two concurrent requests with the same key both run.

## Recover a missed event

Run a reconciliation sweep every few minutes alongside your webhook receiver. Webhooks are the fast path, and the sweep is what makes the integration complete: it catches events missed during an outage, deliveries that succeed only after a document's retention deadline, and runs that end as `unknown`, which send no event.

1. Record every uploaded document `id` with the time of upload.
2. Every few minutes, call `/processing/latest` for documents that have no terminal result yet and were uploaded more than a few minutes ago.
3. Fetch and store results for any that are `completed`, and handle `failed` and `unknown` as if an event had arrived.
4. Keep tracking documents that return `PROCESSING_NOT_FOUND`, and retry with backoff. If your application's deadline is reached, contact support with the document ID and `X-Request-Id` before uploading another copy.
5. Finish each document well before its retention deadline, which can be as soon as 1 hour after processing ends.

To recover deliveries to your endpoint, use **Retry delivery** and **Re-enable** in Studio; see the [Webhooks guide](/api/webhooks.html).

## Retention and deletion

API uploads are deleted 24 hours after upload or 1 hour after processing ends (completed, failed or canceled), whichever comes first. If you sent your own OCR, we delete the file as you sent it once it's imported, and keep only our converted copy, which is deleted with the document. Documents uploaded in Studio aren't deleted automatically. Store results in your own system as soon as processing completes; webhooks and retries don't restore a deleted document.

`DELETE /documents/{id}` permanently deletes a document or folder and its results, and returns `200` with `{"id": "...", "deleted": true}`. Deleting an email upload also deletes its body and attachment documents. Delete only documents your integration uploaded: webhook events also arrive for documents processed in Studio. Deletion finishes shortly after the response. Until then a repeated `DELETE` also returns `200`, and `include_deleted=true` on listings shows the item; afterwards the document returns `404`. Usage and billing records are kept.

## Usage and billing

`GET /usage` and `GET /usage/daily` report document processing started through the API or by uploads in Studio. Both are included in these totals. For pay-as-you-go accounts, API and Studio uploads use the same free page allowance and prepaid wallet. `/usage/daily` returns UTC days keyed by completion date, the last 30 days by default and up to 366 days per request.

Pay-as-you-go accounts (`account.plan=payg`):

- `page_rate_micros` is the price per page in millionths of a US dollar: `30000` means $0.03. Wallet amounts ending in `_cents` are integer US cents, and `wallet.currency` is `USD`.
- New personal accounts receive 10,000 free pages. `wallet.free_window` says how the allowance applies: `lifetime` grants it once, and `rolling_30d` applies it to the last 30 days.
- When processing starts, its pages are reserved: free pages first, then prepaid credit (`free_reserved_pages`, `reserved_cents`). A successful run is charged once. A failed or canceled run releases its reservation and isn't charged.
- `wallet.free_available_pages` and `wallet.available_cents` already exclude reserved amounts; read them directly.
- An Unlimited account has `period.unlimited` and `wallet.free_unlimited` set to `true`. New runs aren't charged, and `free_available_pages` is `null`.
- `period.pages_processed` counts successful pages over the account's lifetime, and `resets_at` is `null`. Ignore `period.start` and `period.end` on these accounts.
- Each upload is processed and charged separately, even for an identical file. Reading results, polling and `pages=` filters aren't charged; they count toward request limits.

Enterprise accounts (`account.plan=enterprise`): usage follows the workspace's billing period. The billing owner sees the whole workspace's `period` counters, `remaining_pages` and `spend`. Other members see their own usage for the period, without `remaining_pages` or `spend`, and their `/usage/daily` shows only their own runs. `spend` amounts are in minor units of `spend.currency`, a lowercase code such as `usd`. `spend` is `null` on custom-invoiced plans and when no price applies to your plan; `spend_note` says why.

## Limits

| Limit                   | Default                                                                                                                                     |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Requests per API key    | 60 per minute.                                                                                                                              |
| Requests per account    | Twice the per-key limit, shared by all the API keys of your user account.                                                                   |
| Requests per IP address | 20 per minute for requests that don't authenticate. When it's reached, every request from that address gets `429` until the minute resets.  |
| Concurrency             | Per-key and per-account limits on concurrent requests and processing runs, when configured for your account.                                |
| Upload                  | One document per request, up to 250 MiB (262,144,000 bytes), plus its OCR file if you send one.                                             |
| OCR file                | Up to 128 MiB (134,217,728 bytes) per upload, raw or gzip-compressed; PDF uploads only.                                                     |
| File name               | 255 characters, including the extension.                                                                                                    |
| Email attachments       | Up to 10 attachments in supported formats per EML or MSG file.                                                                              |
| JSON request body       | 1 MiB (1,048,576 bytes).                                                                                                                    |
| Pages per document      | 2,000.                                                                                                                                      |
| Request timeout         | 30 seconds. For uploads, 120 seconds for storing and registering the file, not counting the time spent sending it. A timeout returns `504`. |

Authenticated responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` for the limit that applied. Rate-limit `429` responses, including the per-IP limit, carry `Retry-After`; concurrency rejections don't, so lower your concurrency instead. Fix authentication errors rather than retrying them, since failed requests count toward the IP address limit.

The page limit is checked during processing, so an accepted upload can still fail with `DOCUMENT_PAGE_LIMIT_EXCEEDED`. Processing time depends on document length and content; use status or webhooks to detect completion.

Your account's limits can differ from these defaults, and the response headers show the limit that applied. To change a limit, email [support@lexselect.io](mailto:support@lexselect.io).
