Pipeline Results
Poll a document processing pipeline's structured rows as JSON with GET /v1/pipelines/{id}/results: default record shape, composed vs documents views, filters, and cells/provenance/audit includes.
GET /v1/pipelines/{id}/results returns a pipeline run's structured rows as poll-able JSON — the same governed read (review holdback, demoted-field projection, __ diagnostics filtering, assembly awareness) that previously existed only as the run.completed webhook or a data-product CSV export. Where GET /v1/pipelines/{id} and /progress report lifecycle only, this endpoint is the row data itself: no data product to create, no share token, no CSV parsing. Poll it directly while the run is still active, since the underlying table is progressive.
Each record's default shape mirrors the pipeline.capture webhook envelope: document_id, filename, run_id (the attributed /v1/run request, or null for a UI/POST /v1/pipelines run), pipeline_id, record_id, per-document status, completed_at, optional metadata/batch_id (present only when set), and a clean fields object. A field a blocking validation gate parked for review serializes null in fields — held values never leave this surface, in any shape, under any include.
The view parameter chooses the record grain. composed (the default whenever the run has produced an assembly product set) returns one row per assembled group — the same rows the data product and CSV exports serve. documents returns one row per pipeline document from the main record set. A run with no assembly only has the documents view; view=composed degrades to it rather than erroring. Watch for anchor-less composed rows: a product record whose anchor could not be resolved (a known gap on the per_group_jobs path) serializes document_id/filename/run_id: null, status: "complete", and completed_at: null — these rows are excluded by any document_id, status, since, or until filter and only appear in unbounded reads.
Filter with document_id (comma-separated, up to 100), run_id (scope to one /v1/run request's own documents — 404 if it does not belong to this pipeline), status (a comma-separated subset of complete, partial, error, processing), and since/until against completed_at. completed_at tracks document processing completion, not value change: a review promotion or an assembly recompose never advances it, and a phase rerun NULLS the completion stamps for every affected document until it re-processes. An incremental poller using since will not see a row whose only change was a review outcome — poll unbounded, or re-poll known-held rows by document_id; pending_review_count on the page is the signal that held values are outstanding.
Add shape with include=cells,provenance,audit (comma-separated). cells adds per-field { value, status, confidence, source, document_id, filename } — source is an opaque-but-stable vocabulary (new values may appear; existing ones never rename). provenance adds a stable projection of the cell's audit trail (kind one of span, assembly_override, auto_adjudication, human, derived, legacy, with an open derived_reason enum) — a held cell's projection is kind-only, with no displaced or previous values. audit adds the full cell-version trail, bounded to 20 records per page by default (structure_pipeline.results_api.audit_max_records) — a broader page 400s with audit_include_too_broad, directing you to narrow with document_id or a smaller limit. For any field whose LATEST version is held, every version in its trail serializes value: null, since the hold mechanism copies the value forward and the prior version would otherwise be an equal leak.
Pagination is cursor-based over the filtered, record_id-ordered list (pagination.next_cursor), never an invented page param — stable even as an append pipeline keeps growing between polls. Per-record status folds roughly 10 internal pipeline_documents states down to four public values: complete, partial, and error pass through unchanged; everything else (queue states, awaiting_ocr, and similar) folds to processing. This deliberately KEEPS partial, unlike GET /v1/run/{id}'s 3-state documents[] fold — a partial row's held fields are the reason to keep polling it.
Poll-loop guidance: this envelope's top-level status is the pipeline's own lifecycle status, and it settles at completed for a single-request pipeline. It never settles for an append pipeline, since each new /v1/run request can reopen it — append consumers should loop on per-record status/completed_at instead, or poll GET /v1/run/{id}/results, whose run-scoped status does settle. Preview pipelines are served here (a preview has a real record set), even though they are excluded from GET /v1/pipelines's listing.
/v1/field-reviews/decisions). This endpoint reports current field state, not the review history behind it./v1/pipelines/{id}/resultsQuery parameters
25Request
Response
Response fields
Response
{
"pipeline_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"spec_id": "sch_uuid_1",
"status": "active",
"view": "documents",
"generated_at": "2026-07-22T09:41:00.000Z",
"columns": [
{ "field_key": "vertragsnummer", "display_name": "Vertragsnummer", "data_type": "string" },
{ "field_key": "mieter_name", "display_name": "Mieter", "data_type": "string" },
{ "field_key": "kaution_betrag", "display_name": "Kaution (EUR)", "data_type": "number" },
{ "field_key": "mietbeginn", "display_name": "Mietbeginn", "data_type": "date" }
],
"data": [
{
"document_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"filename": "mietvertrag-muster-gmbh.pdf",
"run_id": null,
"pipeline_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"record_id": "pd_9f1e2d3c",
"status": "complete",
"completed_at": "2026-07-22T09:38:11.000Z",
"fields": {
"vertragsnummer": "MV-2026-0847",
"mieter_name": "Muster GmbH",
"kaution_betrag": 4500,
"mietbeginn": "2026-08-01"
}
},
{
"document_id": "b7a6c5d4-e3f2-1098-7654-3210fedcba98",
"filename": "mietvertrag-beispiel-ag.pdf",
"run_id": null,
"pipeline_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"record_id": "pd_1a0c681d",
"status": "partial",
"completed_at": "2026-07-22T09:39:47.000Z",
"fields": {
"vertragsnummer": "MV-2026-0848",
"mieter_name": "Beispiel AG",
"kaution_betrag": null,
"mietbeginn": "2026-08-15"
}
}
],
"pagination": { "total": 24, "limit": 2, "has_more": true, "next_cursor": "eyJpZCI6InBkXzFhMGM2ODFkIn0=" },
"pending_review_count": 1,
"links": {
"self": "/v1/pipelines/a1b2c3d4-e5f6-7890-abcd-ef1234567890/results",
"pipeline": "/v1/pipelines/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"progress": "/v1/pipelines/a1b2c3d4-e5f6-7890-abcd-ef1234567890/progress"
}
}A held field (kaution_betrag above, on the partial row) is null in fields no matter what includes you request. Adding include=cells,provenance shows why: the cell carries its pending_approval status, and its provenance projects kind-only, with no text — the reviewer has not yet decided what value, if any, is correct.
Held field detail (include=cells,provenance)
{
"kaution_betrag": null
}
// cells.kaution_betrag
{
"value": null,
"status": "pending_approval",
"confidence": 0.61,
"source": "llm_extraction",
"document_id": "b7a6c5d4-e3f2-1098-7654-3210fedcba98",
"filename": "mietvertrag-beispiel-ag.pdf"
}
// provenance.kaution_betrag — kind-only, no text, no displaced value
{
"kind": "span"
}Errors
Error responses