Skip to main content

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.

Review decisions (who approved or corrected a held field, and why) stay off this surface — they belong to the decision log (/v1/field-reviews/decisions). This endpoint reports current field state, not the review history behind it.
GET/v1/pipelines/{id}/results

Query parameters

document_idstringComma-separated document UUIDs (1-100). Composed rows match on the anchor document.
run_idstringScope to one /v1/run request's own documents (implies view=documents). 404 if it does not belong to this pipeline.
sincestringISO 8601 timestamp. Only records with completed_at at or after this bound; excludes still-processing and anchor-less composed rows.
untilstringISO 8601 timestamp. Only records with completed_at at or before this bound.
statusstringComma-separated subset of complete, partial, error, processing.
viewstringcomposed or documents. Defaults to composed when the run has an assembly product set, else documents.
includestringComma-separated subset of cells, provenance, audit.
limitintegerMaximum records per page (max 100). Default: 25
cursorstringPagination cursor from a previous response's pagination.next_cursor.

Request

Response

Response fields

pipeline_idstringPipeline run UUID.
spec_idstringThe Spec this run compiled from (pipeline.schema_id).
statusstringThe pipeline's own lifecycle status. Never settles for an append pipeline that keeps receiving requests.
viewstringcomposed or documents — the grain actually served.
generated_atstringISO 8601 timestamp this page was generated.
columnsarrayDemotion-projected schema columns: { field_key, display_name, data_type }.
dataarrayThe result records for this page.
data[].fieldsobjectfield_key -> clean value. Held fields are null; demoted fields and __ diagnostics are absent.
data[].cellsobjectPresent only with include=cells. field_key -> { value, status, confidence, source, document_id, filename }.
data[].provenanceobjectPresent only with include=provenance. field_key -> { kind, text?, derived_reason?, source_document_id?, page_index? }.
data[].auditobjectPresent only with include=audit. field_key -> the cell-version trail, held-redacted whole-trail if the latest version is held.
paginationobject{ total, limit, has_more, next_cursor } — cursor keyed on record_id.
pending_review_countintegerHeld cells in this page's records only.
scope_notestringPresent only for the echo-less legacy run_id zero-state — an honest empty page, never a guess.
linksobject{ self, pipeline, progress }.

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

400bad_requestcode is one of invalid_view, invalid_document_ids, invalid_run_id, invalid_time_range, invalid_status, invalid_include, audit_include_too_broad.
401unauthorizedMissing or invalid API key.
404not_foundNo pipeline with this ID exists for your organization, or run_id does not belong to this pipeline.
429rate_limitedToo many requests. Retry after the period indicated in the Retry-After header.