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.
Create Job
Section titled “Create Job”POST /api/v1/extraction-jobs
Request Schema
Section titled “Request Schema”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:
| Field | Accepted values | Effect |
|---|---|---|
documentReview | Integer 1–99; optional | Holds results for approval when an extracted field’s confidence is below the threshold |
deliveryMode | poll (default) or webhook | Use 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:
| Header | Purpose |
|---|---|
Idempotency-Key | Identifies one intended upload so you can retry it safely. |
X-Client-Reference | Connects the job to a record in your system. See Client Reference. |
Example Request
Section titled “Example Request”Create a template named invoice in the app before using this request:
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".
Response
Section titled “Response”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.
Safe Retries
Section titled “Safe Retries”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.
Key Conflicts
Section titled “Key Conflicts”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.
| Request | Response |
|---|---|
First upload with key invoice-1001-upload-1 | 202: new job |
| Retry the same file and settings with that key | 200: existing job |
| Send a corrected file or change settings with that key | 409 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.
Replay Window
Section titled “Replay Window”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 Job Status
Section titled “Get Job Status”GET /api/v1/extraction-jobs/:id
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" }}Job States
Section titled “Job States”| Status | What it means | Your next step |
|---|---|---|
queued | The job is waiting to start. | Wait for a notification or check again after retryAfterMs. |
processing | Extraction is running. The response may include progress. | Wait for a notification or check again after retryAfterMs. |
review_required | Results are waiting for human approval. | Complete review in the app, then retrieve the approved result. |
completed | Processing has finished. | Fetch resultUrl when resultAvailable is true. |
failed | Processing 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 Job Result
Section titled “Get Job Result”GET /api/v1/extraction-jobs/:id/result
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.
Result Errors
Section titled “Result Errors”| Status and code | Your next step |
|---|---|
404 JOB_NOT_FOUND | Check the job ID and environment. The job may have expired. |
409 RESULT_NOT_READY | Wait for processing to finish. |
409 REVIEW_REQUIRED | Complete review in the app. The result becomes available after approval. |
409 JOB_FAILED | Read the job’s status for the processing error. |
410 RESULT_EXPIRED | The stored result is no longer available. A new extraction requires a new job. |
Result Retention
Section titled “Result Retention”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.