Asynchronous processing
Queue a scan or a comparison, then poll the job or receive a webhook
A scan or a comparison reads every page of the file and, for a comparison, reads each page holding a code by OCR. A file of several pages takes tens of seconds, longer than many HTTP clients and proxies wait. Ask for an asynchronous answer instead: the API queues the work, answers 202 at once with a job, and you read the result when it is ready.
The synchronous call stays the default: without the preference, nothing changes.
Request
Send the same request as for POST /2ddoc/scan or POST /2ddoc/compare, with the Prefer header (RFC 7240):
POST /2ddoc/compare
Authorization: Bearer oat_your_key
Prefer: respond-async
Content-Type: multipart/form-data
file: <PDF or image file>The form field async=true does the same, for clients that cannot set a header.
HTTP/1.1 202 Accepted
Location: /2ddoc/jobs/tdj_adbae78350ca4f80bf5857436722635c
Preference-Applied: respond-async{
"id": "tdj_adbae78350ca4f80bf5857436722635c",
"kind": "compare",
"status": "queued",
"step": null,
"document": null,
"createdAt": "2026-10-22T19:54:15.120Z",
"updatedAt": "2026-10-22T19:54:15.120Z",
"finishedAt": null,
"error": null,
"result": null,
"resultAvailableUntil": null,
"statusUrl": "/2ddoc/jobs/tdj_adbae78350ca4f80bf5857436722635c"
}An organization may have 20 jobs queued or running at once; past that, the API answers 429.
Calls made with an SDK token are always answered synchronously, as RFC 7240 lets a server ignore the preference: the app receives its verdict on the response.
Following a job
GET /2ddoc/jobs/{id}
Authorization: Bearer oat_your_key| Field | Description |
|---|---|
status | queued, running, done or failed |
step | While running: scanning, verifying or comparing |
document | While running, in a file of several codes: the one being handled, { "index": 2, "total": 3, "page": 4 } |
result | Once done: the response body the synchronous call would have returned, unchanged |
error | Once failed: a generic reason, never a value of the document |
resultAvailableUntil | The job and its result are readable 15 minutes after the end, then deleted |
While the job runs, the response carries Retry-After: 2. A job belongs to the organization that created it: any other key gets 404.
Webhook
When a job ends, the API posts twoddoc.job.completed to the organization's verification webhook, the endpoint set for the mobile SDK (same signature X-Webhook-Signature: v1=HMAC-SHA256(secret, "<timestamp>.<body>"), same retries). See native SDK, webhook.
{
"id": "5f0b6c1e-0c49-4d6e-9a2b-1f6f3d1c8e21",
"type": "twoddoc.job.completed",
"timestamp": "2026-10-22T19:54:22.640Z",
"data": {
"jobId": "tdj_adbae78350ca4f80bf5857436722635c",
"kind": "compare",
"status": "done",
"documents": [
{ "page": 1, "status": "valid", "documentType": "Avis d'impôt sur les Revenus (V2)", "altered": true, "confidence": 0 }
],
"resultAvailableUntil": "2026-10-22T20:09:22.624Z"
}
}The webhook carries no value read from the document: per code, its page, its signature status and, for a comparison, whether the page contradicts it. Read the fields from GET /2ddoc/jobs/{id} with your key.
Data
The file is kept in memory until the job has run, and at most 30 minutes; the result 15 minutes after the end. Each 2D-DOC verified counts as one verification, as for the synchronous call.