A processing run is the stored record of one request to the parse, extract, or classify endpoint. It holds the input document, the instructions, the result, and what the request cost. Every stored run has a public ID and lives at the run endpoint:
GET https://api.nutrient.io/extraction/runs/{runId}Use runs when a result has to outlive the request that produced it. A synchronous request returns its result in the response body, and that’s the last you see of it unless you stored the run. An asynchronous request returns nothing but a run, so polling the run and downloading its assets is the whole workflow.
When a run is stored
The synchronous endpoints don’t store runs by default. Add storeRun: true to the instructions, and the response carries the run’s ID in the body as runId and in the x-nutrient-run-id response header. The asynchronous endpoints always store their runs, so they reject storeRun: false.
Runs you start from the Data Extraction Studio(opens in a new tab) are stored too, and the Studio’s run history reads the same endpoint. Runs you submit with an API key appear there next to the Studio’s own, with their source shown as API.
Run status
A run’s status tells you where it is in its life:
| Status | Meaning |
|---|---|
queued | Accepted and stored, waiting for a worker to pick it up. Only asynchronous runs pass through this state. |
running | Being processed. |
done | Finished with a result. The run carries what it cost in usage. |
error | Finished without a result. The run carries an error object. |
canceled | Canceled before it finished. Any result was discarded and nothing was charged. |
The API writes a synchronous run when the request completes, so it’s already done or error the first time you can read it. An asynchronous run starts as queued and moves through running to one of the three terminal states. While it’s running, it reports where it is in progress. Refer to the run asynchronously guide for what each terminal state means for billing.
Read a run
Fetch a run by its ID:
curl https://api.nutrient.io/extraction/runs/7KPSBBDRPEZ510NKVN0E42VHBH \ -H "Authorization: Bearer your_api_key_goes_here"import requests
run_id = "7KPSBBDRPEZ510NKVN0E42VHBH"response = requests.get( f"https://api.nutrient.io/extraction/runs/{run_id}", headers={"Authorization": "Bearer your_api_key_goes_here"},)
run = response.json()print(run["status"])const runId = "7KPSBBDRPEZ510NKVN0E42VHBH";const response = await fetch(`https://api.nutrient.io/extraction/runs/${runId}`, { headers: { Authorization: "Bearer your_api_key_goes_here" },});
const run = await response.json();console.log(run.status);The response describes the run and lists the files you can download from it:
{ "id": "7KPSBBDRPEZ510NKVN0E42VHBH", "operation": "extraction_parse", "surface": "api", "status": "done", "label": null, "fileName": "invoice.pdf", "createdAt": "2026-09-23T21:00:34Z", "expiresAt": "2026-10-23T21:00:36Z", "instructions": { "mode": "structure" }, "metadata": { "fileName": "invoice.pdf" }, "metrics": { "pagesProcessed": 2, "processingTimeMs": 115 }, "usage": { "data_extraction_credits": { "cost": 3.0 } }, "assets": [ { "direction": "input", "key": "input", "contentType": "application/pdf", "byteSize": 13264, "downloadUrl": "/extraction/runs/7KPSBBDRPEZ510NKVN0E42VHBH/assets/input" }, { "direction": "output", "key": "result", "contentType": "application/json", "byteSize": 941, "downloadUrl": "/extraction/runs/7KPSBBDRPEZ510NKVN0E42VHBH/assets/result" } ], "error": null, "shared": false, "shareUrl": null, "evaluation": null}operation— Which endpoint produced the run:extraction_parse,extraction_extract,extraction_classify,extraction_form, orextraction_split.surface— Where the run came from:apifor requests with your API key,studiofor runs started in the Studio, andevaluationfor runs an evaluation set produced.instructions— The instructions the request was processed with, so you can tell later how a result was produced.metrics— Pages processed and processing time, once the run is done.usage— The credits the run was charged, under the samedata_extraction_credits.costkey the synchronous response uses. It’s empty until the run isdone, and it stays empty for runs that failed or were canceled, since those cost nothing.assets— The files stored with the run. Refer to the next section.error— Present on failed runs. It carries the HTTPstatusthe request would have returned, therequestId, anddetails.source. For failures you can act on, it also carriesdetails.code.progress— Present only while the run isrunning: the worker’s last report, withphase,percent,pagesTotal,pagesStarted,pagesCompleted, andupdatedAt. The phases arepreparing,processing, andstoring. More will be added, so treat a phase you don’t know as the run still being in progress. The outcome is alwaysstatus, never this field.sharedandshareUrl— Whether the run has an active public share link from the Studio, and the link itself.evaluation— For a run an evaluation set produced, the set, evaluation run, and item it belongs to. It’snullfor every other run.expiresAt— When the run leaves your history, ornullon plans that keep runs indefinitely. Refer to the retention section below.
Download assets
Each entry in assets is a file you can download from the run. The key is the last path segment of the asset endpoint, and the response Content-Type matches the stored file:
curl https://api.nutrient.io/extraction/runs/7KPSBBDRPEZ510NKVN0E42VHBH/assets/result \ -H "Authorization: Bearer your_api_key_goes_here" \ -o result.jsonEvery run carries its input, the document as it was uploaded or downloaded from the URL you sent. A done run adds result, a JSON file holding exactly the body the synchronous endpoint would have returned, with output, metrics, usage, and configuration. A failed run may also carry result, holding the error body instead.
A run that’s still queued or running lists only its input. Asking for its result returns 404 with the code run_asset_not_found until the run is done.
List runs
List your stored runs, newest first, with GET /extraction/runs:
curl "https://api.nutrient.io/extraction/runs?operation=extraction_parse&status=done&page_size=20" \ -H "Authorization: Bearer your_api_key_goes_here"Filter the list with these query parameters. Each filter accepts a single value, comma-separated values, or the parameter repeated.
| Parameter | Description |
|---|---|
operation | Limit to one or more operations, such as extraction_parse. |
surface | Limit to api, studio, or evaluation runs. |
status | Limit to one or more of the statuses above. Without it, the list includes in-flight and canceled runs next to finished ones. |
ui_status | A coarser filter: processed for done runs and error for failed ones. Queued, running, and canceled runs fall in neither bucket. |
mode | Limit to one or more parse modes. Matches instructions.mode on parse runs and instructions.parseConfig.mode on extract runs. |
query | Search by file name, label, or run ID. |
order | Sort order. Defaults to created_at_desc. |
cursor | The opaque cursor from a previous page’s pagination, to fetch the next page. Keep the same filters and order when you pass it. |
page_size | Runs per page, up to 100. Defaults to 50. |
Each item in the list carries the same fields as the run detail, except assets and error. Fetch a single run when you need those.
Delete a run
Delete a run to remove it from your history and, on plans that don’t retain data, to purge its stored files:
curl -X DELETE https://api.nutrient.io/extraction/runs/7KPSBBDRPEZ510NKVN0E42VHBH \ -H "Authorization: Bearer your_api_key_goes_here"A successful delete answers 204, and deleting a run that’s already gone answers 404. A run that’s still queued or running can’t be deleted, so the request answers 409 with the code run_in_flight until you cancel the run. Refer to the run asynchronously guide for canceling.
Retention
How long a run stays readable depends on your plan. The run’s expiresAt tells you the exact time; on plans that keep runs indefinitely, it’s null. Once a run expires, reading it or any of its assets answers 410 with the code run_expired. The API removes the run and its files shortly after.
The retention window starts when the run settles, not when you submitted it. An asynchronous run never expires while it’s queued or running, and its window starts fresh when it reaches done, error, or canceled. A long wait therefore never eats into the time you have to read the result.
For what Nutrient keeps beyond your run history and how to opt out, refer to the privacy guide.
Next steps
- Refer to the run asynchronously guide to submit long documents without holding a connection open, cancel runs, and retry safely.
- Refer to the error handling guide for the run endpoint status codes.
- Refer to the API reference for the full run schema and every list parameter.