Skip to content

Asynchronous Extraction

Submit a document, save the job ID, and retrieve the result when it is ready. Use webhooks to notify your backend when processing finishes. You can also check a job’s status using its ID.

All three endpoints require Authorization: api-key <environment-api-key>. Use a key from the environment where you created the job.

POST /api/v1/extraction-jobs

Send one file and your extraction settings as top-level fields. Async jobs accept the same extraction fields and file formats and limits as sync extraction, plus:

FieldAccepted valuesEffect
documentReviewInteger 1–99; optionalHolds results for approval when an extracted field’s confidence is below the threshold
deliveryModepoll (default) or webhookUse webhook for completion and failure notifications, or poll to check status yourself

To enable review at a 92% confidence threshold, send "documentReview": 92 in JSON or -F "documentReview=92" in multipart. Omit the field to disable review. Status and result reads are available with either delivery mode.

Optional headers:

HeaderPurpose
Idempotency-KeyIdentifies one intended upload so you can retry it safely.
X-Client-ReferenceConnects the job to a record in your system. See Client Reference.

Create a template named invoice in the app before using this request:

Terminal window
curl -X POST "https://api.terrafai.com/api/v1/extraction-jobs" \
-H "Authorization: api-key $API_KEY" \
-H "Idempotency-Key: invoice-1001-upload-1" \
-H "X-Client-Reference: account-42:invoice-1001" \
-F "file=@invoice.pdf;type=application/pdf" \
-F "templateName=invoice"

To receive notifications, configure a webhook for the template and add -F "deliveryMode=webhook".

A new job returns HTTP 202 Accepted:

{
"success": true,
"requestId": "req_9d6a4f3c-0d7b-4ab1-9b1a-5b27499e9c2f",
"clientReference": "account-42:invoice-1001",
"job": {
"id": "2c0d2f0e-1f4e-4c11-a0d6-64cc7b71697d",
"status": "queued",
"statusUrl": "/api/v1/extraction-jobs/2c0d2f0e-1f4e-4c11-a0d6-64cc7b71697d",
"retryAfterMs": 5000
}
}

Save job.id as soon as you receive it. statusUrl is relative to your API base URL. clientReference is included when you supplied it on creation.

Use Idempotency-Key when a timeout or interrupted connection could leave you unsure whether an upload succeeded. Generate the key before sending the file and save it with your upload record.

For example, use invoice-1001-upload-1 for the first extraction of an invoice. If the connection drops, retry with that same key, file, MIME type, and settings. Within the replay window, an accepted request returns the existing job with HTTP 200 and X-Idempotent-Replay: true.

Use a new key when you intentionally process a new or corrected document, change extraction settings, or reprocess after editing a template. Keep the same client reference if you want those extractions grouped under one business record.

Reusing a key with different document content, a different MIME type, or changed extraction settings returns 409 IDEMPOTENCY_KEY_CONFLICT. The conflicting request creates no new job.

RequestResponse
First upload with key invoice-1001-upload-1202: new job
Retry the same file and settings with that key200: existing job
Send a corrected file or change settings with that key409 IDEMPOTENCY_KEY_CONFLICT

If you intended a retry, send the original file and settings. If you intended new work, use a new key, such as invoice-1001-upload-2. Renaming the file alone does not make it a different request.

Keys are scoped to the calling environment. Use a non-empty, single-line value of up to 255 characters, without carriage returns, line feeds, or NUL characters. Surrounding whitespace is removed.

If HTTP 409 says a request with the key is still being created, wait briefly and retry the same request. See Authentication & Errors for other upload errors.

Duplicate protection lasts for your environment’s async retention period, starting when the job is accepted. The default is seven days. Reading a job or retrying its upload does not extend this window.

Once you have a job ID, use status and result reads to resume work. After the replay window ends, another upload with the same key can create a new job, so keep your own record of completed work for long-term duplicate prevention.

GET /api/v1/extraction-jobs/:id

Terminal window
curl "https://api.terrafai.com/api/v1/extraction-jobs/$JOB_ID" \
-H "Authorization: api-key $API_KEY"

A status response returns HTTP 200:

{
"success": true,
"clientReference": "account-42:invoice-1001",
"job": {
"id": "2c0d2f0e-1f4e-4c11-a0d6-64cc7b71697d",
"status": "completed",
"createdAt": "2026-03-12T10:00:00.000Z",
"updatedAt": "2026-03-12T10:01:03.000Z",
"completedAt": "2026-03-12T10:01:03.000Z",
"resultAvailable": true,
"resultUrl": "/api/v1/extraction-jobs/2c0d2f0e-1f4e-4c11-a0d6-64cc7b71697d/result"
}
}
StatusWhat it meansYour next step
queuedThe job is waiting to start.Wait for a notification or check again after retryAfterMs.
processingExtraction is running. The response may include progress.Wait for a notification or check again after retryAfterMs.
review_requiredResults are waiting for human approval.Complete review in the app, then retrieve the approved result.
completedProcessing has finished.Fetch resultUrl when resultAvailable is true.
failedProcessing ended with an error.Read job.error.code and job.error.message for details.

Review status may include reviewRequiredAt, reviewThreshold, and reviewFlaggedFieldCount. Completed and failed jobs may include completedAt or failedAt respectively.

For repeated status checks, wait at least job.retryAfterMs between requests. The endpoint also supplies Retry-After; follow it if you receive 429 RATE_LIMIT. Pause checks while a job awaits review and stop once it completes or fails.

An unknown or expired job, or a job in another environment, returns 404 JOB_NOT_FOUND.

GET /api/v1/extraction-jobs/:id/result

Terminal window
curl "https://api.terrafai.com/api/v1/extraction-jobs/$JOB_ID/result" \
-H "Authorization: api-key $API_KEY"

A completed result returns HTTP 200:

{
"success": true,
"clientReference": "account-42:invoice-1001",
"job": {
"id": "2c0d2f0e-1f4e-4c11-a0d6-64cc7b71697d",
"status": "completed",
"completedAt": "2026-03-12T10:01:03.000Z"
},
"data": {
"completionTime": 63.2,
"originalName": "invoice.pdf",
"processedPages": 1,
"documents": [
{
"extracted": true,
"documentName": "Invoice",
"averageDocumentConfidence": 98,
"sourcePages": [1],
"content": {
"invoiceNumber": { "value": "INV-1001", "confidence": 98, "page": 1 }
}
}
]
}
}

The data object uses the same response fields as sync extraction. Check each document’s extracted flag and handle missing field values before using the data; see Document Outcomes.

If you requested returnText=true, read the detected page text from each document’s returnedText array. Split results can include sharedPages to identify source pages used by more than one document.

Status and codeYour next step
404 JOB_NOT_FOUNDCheck the job ID and environment. The job may have expired.
409 RESULT_NOT_READYWait for processing to finish.
409 REVIEW_REQUIREDComplete review in the app. The result becomes available after approval.
409 JOB_FAILEDRead the job’s status for the processing error.
410 RESULT_EXPIREDThe stored result is no longer available. A new extraction requires a new job.

Retrieve and store results your system needs. Completed results are kept for your environment’s retention period, which defaults to seven days from completion or review approval. This is separate from the idempotency window, which starts at job acceptance.

An expired result returns 410 RESULT_EXPIRED while its job record remains available. Once the job record also expires, reads return 404 JOB_NOT_FOUND.