Skip to content

Multi-page documents

Updated

When this applies#

A PDF of more than 3 pages is not processed synchronously. documents splits it and returns 202 instead of 200.

This is automatic and cannot be disabled. Any integration that accepts arbitrary PDFs must handle the 202 branch — otherwise it will appear to work in testing on small files and fail in production on real documents.

Images are always single-page and always synchronous.

Limits#

LimitValue
Sync threshold3 pages
Maximum pages150
Maximum file size10 MB
Job TTL2 hours from split
Open jobs per key5
Chunk size3 pages

The three calls#

1. Upload — returns a manifest#

curl -X POST https://api.docxtract.io/v3.1/documents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@50-page.pdf" \
  -F 'options={"model":"invoice"}'
{
  "success": true,
  "data": [
    { "job_id": "a1b2…", "pages": "1-3" },
    { "job_id": "c3d4…", "pages": "4-6" }
  ],
  "job_id": "parent99…",
  "pages": 50,
  "expires_at": "2026-08-23T12:00:00Z"
}

data is the chunk list. The root job_id is the parent — you need both.

Warning
The response also contains a result_url field. It has been emitting a path that 404s. Ignore it and build the URL yourself: https://api.docxtract.io/v3.1/result?job_id={parent job_id}.

2. Process each chunk#

Once per chunk job_id, sequentially or in parallel:

curl -X POST "https://api.docxtract.io/v3.1/process?job_id=a1b2…" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F 'options={"model":"invoice"}'

The model is chosen here, per chunk — the split itself is model-independent.

{ "success": true, "data": null, "job_id": "a1b2…", "status": "done",
  "code": "chunk_processed", "pages": "1-3", "chunks_done": 1, "chunks_total": 17 }

data is always null; content comes from step 3. A code of chunk_already_processed means the chunk was already done and the call replayed harmlessly.

Tip
Retrying a chunk is safe. Replay is idempotent and not double-charged, and credits are deducted per chunk only after the result is persisted.

3. Collect the result#

curl "https://api.docxtract.io/v3.1/result?job_id=parent99…" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "success": true,
  "data": { },
  "job_id": "parent99…",
  "status": "complete",
  "pages_total": 50,
  "pages_processed": 50,
  "chunks_done": 17,
  "chunks_total": 17,
  "credits_used": 50
}

status is complete or partial. A partial response adds pending_pages and failed_pages, which makes this endpoint usable as a progress poll — it is a pure read and re-fetchable any number of times within the TTL.

Finalizing#

result?job_id=…&finalize=true permanently deletes all extracted data for the job after responding.

Warning
This is irreversible. Do not finalize until the result is stored on your side. There is no recovery, and the job cannot be re-collected.

Without finalize, data is cleaned up automatically when the 2-hour TTL expires.

Mixing models across chunks#

Permitted, but the collect response then carries a mixed_models warning and the combined result may be internally inconsistent. Use one model per document unless you have a specific reason.

  1. POST documents.php — inspect the status code, not just the body
  2. On 200, you are done
  3. On 202, keep the parent job_id and the chunk list
  4. Call process per chunk with bounded parallelism; retry failures with backoff
  5. Call result with the parent job_id
  6. If status is partial, retry the chunks named in failed_pages
  7. Store the result, then optionally finalize=true

Watch the clock throughout — everything must complete inside the 2-hour TTL, after which chunks are unrecoverable and the document must be re-uploaded.

Official SDKs (Phase 2 onward) collapse all of this into a single extract() call.