Async Task Model
Uploading a document doesn't process it, and starting processing doesn't finish it in the same request. Every document and batch moves through a fixed, closed set of statuses — this page covers that lifecycle end to end.
Status lifecycle
Every document (and, in aggregate, every batch) is always in exactly one of five states:
queued → processing → succeeded | failed | canceledqueued— uploaded and/or waiting for a worker to pick it up.processing— a worker has claimed the document and is actively extracting data from it.succeeded— extraction finished;line_itemsin the results response is populated.failed— extraction could not complete for this document (this is a document-level outcome, not an API-call error — see the note onprocessing_errorin Error Handling).canceled— the document was removed from the queue before processing started (for example, its batch was deleted while it was stillqueued).
This five-value set is a public contract, deliberately decoupled from whatever internal states the system uses behind the scenes. New internal intermediate states can be introduced later without ever adding a sixth value here — every v1 response you parse today will keep working.
The timestamp chain
Both document (GET /documents/{id}) and batch (GET /batches/{batch_name}/results) responses carry the same three fields, so you can tell "queued but stuck" apart from "actively running" apart from "genuinely done":
| Field | Set when |
|---|---|
created_at | The document was uploaded (or, for a batch, the earliest upload in it). |
started_at | A worker claimed the document and began processing it. Stays null while status is still queued. |
completed_at | The document reached a terminal state — succeeded, failed, or canceled. Stays null while still queued or processing. At the batch level, stays null until every document in the batch has one — a batch with anything still in flight isn't "completed" yet, even if some of its documents already finished. |
Polling vs. webhooks
You have two ways to find out when a batch is done:
- Polling — call
GET /batches/{batch_name}periodically and checkall_done. Simple, no infrastructure required on your side, but wastes requests if you poll too aggressively (see Rate Limits for the 120/minute ceiling on status endpoints) or too slowly (added latency before you notice completion). - Webhooks — register a callback URL once with
PUT /batches/{batch_name}/webhookand get notified the moment the batch finishes, instead of asking repeatedly. See the Webhooks guide — it also covers what happens if you add more documents to a batch and process it again after it already notified you once (short version: you get notified again automatically, no extra registration needed).
For a single small batch processed once, polling a few times is simplest. For anything higher-volume, or where you don't want a request loop sitting around waiting, webhooks are the better fit.