Run asynchronously
The parse, extract, classify, and split endpoints provide asynchronous routes. These routes accept the same request formats and return a processing run instead of the result:
POST https://api.nutrient.io/extraction/parse/asyncPOST https://api.nutrient.io/extraction/extract/asyncPOST https://api.nutrient.io/extraction/classify/asyncPOST https://api.nutrient.io/extraction/split/asyncThe API returns 202 Accepted with the run in the queued state. Poll the run until processing finishes. If the run succeeds, download its result asset.
Asynchronous routes process long documents and batches without keeping connections open. They also support clients that can’t wait for processing, such as webhook handlers and queue workers.
For run details and response fields, refer to the processing runs guide.
Prerequisites
Your workspace needs the asynchronous runs feature enabled. Asynchronous splitting also requires the Split feature. The API returns HTTP 404 when a required feature isn’t enabled.
Submit a run
Use the synchronous endpoint’s request format with the asynchronous path. Send a multipart upload with file and instructions, or a JSON body with url.
Asynchronous routes also accept an assetId from POST /extraction/uploads instead of a file or URL.
The following examples submit a parse request:
curl -i -X POST https://api.nutrient.io/extraction/parse/async \ -H "Authorization: Bearer your_api_key_goes_here" \ -F "file=@document.pdf" \ -F 'instructions={"mode":"agentic"}'import requests
with open("document.pdf", "rb") as file: response = requests.post( "https://api.nutrient.io/extraction/parse/async", headers={"Authorization": "Bearer your_api_key_goes_here"}, files={"file": file}, data={"instructions": '{"mode":"agentic"}'}, )
if response.status_code != 202: raise RuntimeError(f"HTTP {response.status_code}: {response.text}")
run = response.json()print(run["id"], run["status"])import fs from "node:fs";
const form = new FormData();form.append("file", new Blob([fs.readFileSync("document.pdf")]), "document.pdf");form.append("instructions", JSON.stringify({ mode: "agentic" }));
const response = await fetch("https://api.nutrient.io/extraction/parse/async", { method: "POST", headers: { Authorization: "Bearer your_api_key_goes_here" }, body: form,});
if (response.status !== 202) { throw new Error(`HTTP ${response.status}: ${await response.text()}`);}
const run = await response.json();console.log(run.id, run.status);The HTTP 202 response includes the run ID in its body and the following headers:
HTTP/2 202location: /extraction/runs/7KPSBBDRPEZ510NKVN0E42VHBHx-nutrient-run-id: 7KPSBBDRPEZ510NKVN0E42VHBHThe body contains the same run details as GET /extraction/runs/{runId}. A new run has status: queued, empty usage, and only the input asset.
The API checks credits when you submit a request. Insufficient credits return HTTP 402 without queuing a run. The API charges for the run when processing finishes.
The API stores every asynchronous run. It rejects storeRun: false in the instructions with HTTP 400.
Poll for the result
Poll the run until its status is done, error, or canceled. Download the result asset only when the status is done:
curl https://api.nutrient.io/extraction/runs/7KPSBBDRPEZ510NKVN0E42VHBH \ -H "Authorization: Bearer your_api_key_goes_here"
curl https://api.nutrient.io/extraction/runs/7KPSBBDRPEZ510NKVN0E42VHBH/assets/result \ -H "Authorization: Bearer your_api_key_goes_here" \ -o result.jsonimport time
import requests
headers = {"Authorization": "Bearer your_api_key_goes_here"}run_url = f"https://api.nutrient.io/extraction/runs/{run['id']}"
delay = 2while True: run = requests.get(run_url, headers=headers).json() if run["status"] in ("done", "error", "canceled"): break time.sleep(delay) delay = min(delay * 2, 30)
if run["status"] != "done": raise RuntimeError(f"run ended {run['status']}: {run['error']}")
result = requests.get(f"{run_url}/assets/result", headers=headers).json()print(result["output"])const headers = { Authorization: "Bearer your_api_key_goes_here" };const runUrl = `https://api.nutrient.io/extraction/runs/${run.id}`;
let delay = 2000;let settled;while (true) { settled = await (await fetch(runUrl, { headers })).json(); if (["done", "error", "canceled"].includes(settled.status)) break; await new Promise((resolve) => setTimeout(resolve, delay)); delay = Math.min(delay * 2, 30000);}
if (settled.status !== "done") { throw new Error(`run ended ${settled.status}: ${JSON.stringify(settled.error)}`);}
const result = await (await fetch(`${runUrl}/assets/result`, { headers })).json();console.log(result.output);Wait at least two seconds between polling requests. Increase the interval while processing continues.
Polling and submission requests share a rate limit, so frequent polling reduces capacity for new submissions. Frequent polling can exhaust a test key’s limit within seconds.
While the run is running, the response includes a progress object with the worker’s latest report. It contains the current phase, a percent estimate when available, and page counters.
Use progress to display processing progress, not to determine the outcome. Continue polling until status is done, error, or canceled. For field details, refer to the processing runs guide.
The result asset contains the response body from the synchronous endpoint. Its fields depend on the endpoint.
For the output format, refer to the parse, extract, classify, and split guides.
Split results depend on the selected output format:
- With
jsonorboth, the result includes page ranges. - With
ziporboth, the run includes azipasset, andoutput.downloadUrlin the result points to it. - With
zip, the result omits page ranges.
Read the outcome
The final status identifies the outcome and whether the API charged for the run:
| Status | What it means |
|---|---|
done | The result asset contains the output. The API charged once, and usage.data_extraction_credits.cost reports the amount. |
error | No result. Check error.status and error.details.source. Status 402 with source billing means processing finished, but billing failed. The API withholds the result and charges nothing. Other errors mean processing failed without a result or charge. |
canceled | You canceled the run before completion. The API discarded any later engine output and charged nothing. |
You can resubmit a failed run. If you used an idempotency key, use a new one. Failed runs retain their keys until deletion, so reusing the key returns the failed run instead of creating another.
Each run has a 15-minute processing limit. Runs that exceed this limit end with status error and incur no charge. Split longer documents into smaller files. Submit each file separately.
Workers automatically resume interrupted runs. Repeated interruptions end the run with status error, and the API charges nothing for those attempts.
Cancel a run
Cancel a run that hasn’t finished:
curl -X POST https://api.nutrient.io/extraction/runs/7KPSBBDRPEZ510NKVN0E42VHBH/cancel \ -H "Authorization: Bearer your_api_key_goes_here"The API returns HTTP 200 with the run’s status set to canceled. Cancellation stops a queued run before processing starts.
For a running run, the engine continues processing, but the API marks the run canceled and discards the result. The API doesn’t charge for canceled runs.
Canceling an already canceled run returns HTTP 200 again. Canceling a run with status done or error returns HTTP 409 with code run_not_cancelable.
The API rejects deletion requests for queued or running runs. Cancel the run before deleting it.
Retry safely with an idempotency key
A network failure can prevent you from receiving HTTP 202 after the API accepts your request. Retrying without an idempotency key can create duplicate runs and charges.
Include an Idempotency-Key header when submitting the request:
curl -i -X POST https://api.nutrient.io/extraction/parse/async \ -H "Authorization: Bearer your_api_key_goes_here" \ -H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \ -F "file=@document.pdf" \ -F 'instructions={"mode":"agentic"}'The key contains 1 to 255 printable characters. Its scope covers your account and the operation, so the same key identifies separate runs for parse and extract.
Retrying the same request with the same key returns HTTP 202 without creating another run. The response includes the original run ID and an Idempotency-Replayed: true header. Reusing the key for a different request returns HTTP 409 with code idempotency_key_reused.
The request’s identity includes the instructions, engine version, processor reference, and input bytes. For url input, the API downloads the document on every submission, including retries.
The API replays a request only if the URL returns the same bytes. Changed content causes HTTP 409. If the URL no longer resolves, the submission fails.
For URLs whose content can change, retain the run ID from the first HTTP 202 response. Poll that run instead of resubmitting the request. Alternatively, upload the file.
The key identifies the request, not an individual attempt. Canceled, failed, and expired runs retain their keys until deletion, so retries return the existing run.
To process the request again, use a new key or delete the existing run to release its key. Synchronous endpoints ignore the header.
Errors
Asynchronous requests return the same validation and billing errors as the synchronous endpoints, plus the errors below:
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | storeRun: false, or an Idempotency-Key that doesn’t contain 1 to 255 printable characters. |
| 402 | Insufficient credits or run storage. The API didn’t queue the request. | |
| 409 | idempotency_key_reused | Another request already uses this Idempotency-Key. |
| 409 | run_not_cancelable | The run you tried to cancel has already finished. |
| 409 | run_in_flight | The run you tried to delete is still queued or running. Cancel it first. |
| 410 | run_expired | The run has passed its retention window. |
| 503 | run_enqueue_failed | The API couldn’t queue the run and stored nothing. Submit the request again. |
Billing errors can include error, code, and message instead of errorMessage. Use the HTTP status to detect submission failures.
Refer to the error handling guide for response formats.
Next steps
Use these references for run management and response details:
- Refer to the processing runs guide for reading, listing, and deleting runs, and for retention.
- Refer to the error handling guide for status codes and error response formats.
- Refer to the API reference for the full request and run schemas.