---
title: "Get a pipeline's structured results (poll-able)"
method: GET
path: "/v1/pipelines/{id}/results"
tags: ["Pipelines"]
---

# Get a pipeline's structured results (poll-able)

`GET /v1/pipelines/{id}/results`

## Request

- Base URL: `https://api.talonic.com`
- URL: `https://api.talonic.com/v1/pipelines/{id}/results`
- Auth: HTTP bearer

The pipeline run's structured rows, poll-able as 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. The default record shape mirrors the `pipeline.capture` webhook envelope: `document_id`, `filename`, `run_id`, `pipeline_id`, `record_id`, `status`, `completed_at`, optional `metadata`/`batch_id`, and clean `fields`. Held (`pending_approval`) values never leave this surface — `fields` serializes `null` for them.

`view=composed` (the default when the run has an assembly product set) returns one row per assembled group, the same rows the data product and CSV exports serve; `view=documents` returns one row per pipeline document from the main record set. A non-assembly run only has the documents view — `view=composed` degrades to it rather than erroring. An **anchor-less composed row** (the product record's anchor could not be resolved — a known `per_group_jobs` identity-stamp gap) serializes `document_id`/`filename`/`run_id: null`, `status: "complete"`, and `completed_at: null`; such rows are excluded by any `document_id`, `status`, `since`, or `until` filter and appear only in unbounded reads.

`since`/`until` filter on `completed_at`, which tracks DOCUMENT PROCESSING completion, not value change: a later review promotion or assembly recompose never advances it, and a phase rerun NULLS the completion stamps for every affected document until it re-processes. Poll unbounded (or by `document_id`) to pick up review outcomes; `pending_review_count` on the page is the signal that held values are outstanding.

A field backed by a structured subschema is projected to its declared subfield types on this poll — in both the `fields` map and the `?include=cells` `cells[].value` — matching the `run.completed` webhook; a `number`/`boolean` subfield parse-miss serializes `null` and an `enum`/`date` miss passes through raw (see the `ResultsCell`/`ResultsRecord` schemas and the Webhooks event catalog for the full per-type contract). The `include=audit` trail is the raw history view and is NOT type-projected.

`include=cells,provenance,audit` (comma-separated) add shape beyond `fields`. `cells` adds per-field `{ value, status, confidence, source, document_id, filename }` — `source` is an opaque-but-stable vocabulary; new values may appear over time. `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 — no displaced/previous values. `audit` adds the full cell-version trail, bounded to `structure_pipeline.results_api.audit_max_records` (default 20) records per page — a broader page 400s with `audit_include_too_broad`. For any field whose LATEST version is held, every version in its audit trail serializes `value: null` (the hold mechanism copies the value forward, so the prior version is an equal leak).

Pagination is house cursor pagination (`pagination.next_cursor`, keyed on `record_id`), never an invented `page` param. Per-record `status` folds ~10 internal states to four public values — `complete`, `partial`, and `error` pass through, everything else (queue/phase states, `awaiting_ocr`, …) folds to `processing`. This deliberately KEEPS `partial`, unlike `GET /v1/run/{id}`'s 3-state `documents[]` fold, because a partial row's held fields are the reason to keep polling. Poll this envelope's `status` until `completed` for a single-request pipeline; an append pipeline never settles at the pipeline level, so append consumers should loop on per-record `status`/`completed_at` or poll `GET /v1/run/{id}/results` instead, whose run-scoped status does settle. Preview pipelines are served. Requires read scope.

## Path parameters

- `id` string, uuid, required

## Query parameters

- `document_id` string
- `run_id` string, uuid
- `since` string, date-time
- `until` string, date-time
- `status` string
- `view` 'composed' | 'documents'
- `include` string
- `limit` integer
- `cursor` string

## Response `200`

The pipeline's results page.

- PipelineResultsResponse
  - `pipeline_id` string, uuid, required
  - `spec_id` string, uuid, required: pipeline.schema_id — the Spec this run compiled from.
  - `status` string, required: The pipeline's own lifecycle status. Never settles for an append pipeline that keeps receiving requests.
  - `view` 'composed' | 'documents', required
  - `generated_at` string, date-time, required
  - `columns` ResultsColumn[], required
    - `field_key` string, required: Stable machine key. Rows in `data` are keyed by this, never by the label.
    - `display_name` string, required: Human-readable column label, always present. A stored display name (a Spec field's title or a minted registry display name) wins; otherwise it is derived from `field_key`, e.g. `charge_type` → `Charge Type`.
    - `data_type` string, required
  - `data` ResultsRecord[], required
    - `document_id` string, uuid, nullable, required: Null only for an anchor-less composed row (the product record's anchor could not be resolved).
    - `filename` string, nullable, required
    - `run_id` string, uuid, nullable, required: The attributed /v1/run request id (echo containment, newest-for-pipeline fallback restricted to echo-less legacy rows); null for a UI or POST /v1/pipelines run.
    - `pipeline_id` string, uuid, required
    - `record_id` string, required: The row's record identity — the pipeline_document id on the main set, the product record id on the composed set.
    - `status` 'complete' | 'partial' | 'error' | 'processing', required: Folded from ~10 internal pipeline_documents states. Deliberately keeps partial (unlike GET /v1/run/{id}'s documents[].status, which folds partial into completed).
    - `completed_at` string, date-time, nullable, required: COALESCE of the last per-phase completion stamp. Tracks document PROCESSING completion, not value change — a review promotion or assembly recompose never advances it, and a phase rerun nulls it until the document re-processes.
    - `metadata` object: Caller tags; present only when set.
    - `batch_id` string: Present only when set.
    - `duplicate_of` object: Present only on a run-scoped duplicate occurrence (GET /v1/run/{id}/results, or the run_id filter) — the canonical whose bytes/OCR it reused, from the request's documents[] echo.
      - `id` string, uuid, required
      - `filename` string, nullable, required
    - `fields` object, required: field_key -> clean value. Held (pending_approval) fields serialize null; demoted fields and __ diagnostics are absent. A structured-subschema field is emitted TYPED (subfields projected to their declared data_type; number/boolean parse-miss -> null, enum/date miss and nested values -> raw), matching the cells[].value projection and the run.completed webhook.
    - `cells` object: Present only when include=cells. Keyed by field_key.
    - `provenance` object: Present only when include=provenance. Keyed by field_key.
    - `audit` object: Present only when include=audit. Keyed by field_key; the cell-version trail.
  - `pagination` ResultsPagination, required
    - `total` integer, required
    - `limit` integer, required
    - `has_more` boolean, required
    - `next_cursor` string, nullable, required: Opaque cursor keyed on record_id. Stable under concurrent appends.
  - `pending_review_count` integer, required: Held cells in the current page's records only.
  - `scope_note` string: Present only for the echo-less legacy run_id zero-state — a 200 empty page, never a guess.
  - `links` object, required
    - `self` string, required
    - `pipeline` string, required
    - `progress` string, required

## Other responses

- `400`: Invalid query parameters. code is one of invalid_view, invalid_document_ids, invalid_run_id, invalid_time_range, invalid_status, invalid_include, audit_include_too_broad.
- `401`: Missing or invalid API key.
- `404`: Pipeline not found, or run_id does not belong to this pipeline/organization.
- `429`: Daily rate limit exceeded.

---

[API](https://skmtc.dev/talonic/apis/talonic-api.md) · [All operations](https://skmtc.dev/talonic/apis/talonic-api/llms.txt) · [OpenAPI document](https://skmtc.dev/talonic/apis/talonic-api/revisions/678fe5f8c9c3?raw)
