Skip to main content

Run a Spec

Upload documents and run a Spec pipeline in a single call with POST /v1/run: mixed file and URL inputs, optional batch_id and metadata tagging, 202 with a poll URL.

The Run endpoint, POST /v1/run, is the one-call front door for Spec pipelines: send one or more documents together with a spec_id, and the platform ingests every input, waits for OCR, compiles the Spec's saved rail into a pipeline, and starts it — extraction, resolution, validation, and delivery exactly as composed in the Spec editor. It replaces the two-step "upload documents, then POST /v1/pipelines" flow for integrations that push files as they arrive.

Inputs are flexible: attach up to 20 files as multipart parts, pass remote file_urls for documents the platform should download, or mix both in one call. At least one input is required. The endpoint always answers 202 Accepted immediately with a run_id and a poll_url — ingestion and the pipeline run continue asynchronously, so the caller never blocks on OCR or LLM work.

Two optional tagging fields ride along and are stamped on every document the call ingests: batch_id, an opaque grouping key for correlating the run with an external job or batch, and metadata, a flat key-value object for anything else the caller wants to carry through. Both are echoed back by GET /v1/run/:id, and both surface on the ingested documents (batch_id / metadata on the document reads).

For a multi-file call, file_metadata tags individual inputs instead of stamping the same bag on every document: a JSON string keyed by each file's uploaded filename (or the exact file_urls entry), each value a flat object with the same shape and limits as metadata. Per file, its bag is merged over the call-level metadata — per-file wins on a shared key — and the resolved, per-input EFFECTIVE bag is what actually lands on the document and is echoed back in documents[].metadata on both this response and GET /v1/run/:id.

The 202 response carries a documents[] array — one entry per input, in the order you sent them — so you always know which platform document each upload became. Each entry has document_id, filename, size_bytes, and source (file or file_url). document_id is the canonical document: for a byte-identical re-upload it resolves to the existing document already on the platform rather than minting a new one, and the entry additionally carries deduplicated: true plus linked_document_id, the id of the thin per-upload link row created for this call. A first-time upload carries deduplicated: false and no linked_document_id.

The ingestion_target field controls how deep ingestion goes after OCR. The default, ingest, makes every document askable: once a document's OCR markdown is ready, Stage-2 field capture runs asynchronously in the background, extracting every field into the workspace corpus that [POST /v1/ask](post-ask) answers over and feeding the field registry. Capture follows the answers, it never delays them: the Spec pipeline starts the moment OCR markdown is ready, without waiting for capture. Pass extract to restore markdown-only ingestion: no background capture runs, and the documents do not join the askable corpus.

Unlike POST /v1/process, this endpoint does not deduplicate submissions — every call starts a fresh run. Byte-identical documents are still content-deduplicated at the ingestion layer (the run reads the canonical document's data).
POST/v1/run

Multipart form fields

spec_id*uuidThe Spec whose saved rail runs. Find it in the Spec editor URL or via GET /v1/schemas.
filesfile[]Zero or more document files (max 20 per call; per-file size capped by your tier). At least one of files / file_urls is required.
file_urlsstring[]Zero or more remote document URLs the platform downloads (SSRF-guarded).
namestringOptional display name for the run (max 200 chars).
batch_idstringOptional opaque grouping key (max 200 chars), stamped on every ingested document and echoed by GET /v1/run/:id.
metadatastringOptional JSON string of a FLAT object { key: string | number | boolean | null }. Nested objects/arrays are rejected 400. Limits: ≤50 keys, key ≤128 chars, string value ≤1024 chars. Persisted on the run and stamped on every ingested document.
file_metadatastringOptional JSON string of a map { inputKey: { key: string | number | boolean | null } } for tagging individual files in a multi-file call. inputKey is the file's uploaded filename (matching what documents[] echoes back) or the exact file_urls entry. Per key, the file's bag is merged over the call-level metadata — per-file wins, and null overrides rather than deletes. A key matching no input 400s as invalid_metadata; a key matching several same-named files applies to all of them. Capped at 20 entries and 64 KB of decoded JSON.
pipeline_modestringOptional: "new" or "append". Overrides the Spec's configured pipeline mode for this call. append adds the documents to the newest eligible existing pipeline on the Spec instead of creating one, so bulk uploads accumulate into a single growing table. Omitted → the Spec's own setting (default new).
ingestion_targetstringingest (default) or extract. ingest kicks async Stage-2 field capture after OCR so the documents become askable via POST /v1/ask; the Spec pipeline starts on the OCR markdown either way and never waits for capture. extract is markdown-only: no capture, documents are not askable. Default: ingest

Append mode

By default every /v1/run call creates its own pipeline. When the Spec's Source stage sets pipeline mode: append (or the call passes pipeline_mode=append), the platform instead appends the call's documents to the newest eligible existing pipeline for the Spec — so pushing 1,000 documents over many calls produces one growing table instead of one pipeline per call. A pipeline is eligible while its configuration still matches the Spec (schema fields, policies, validation gates, assembly and review config) and it is under the append size cap; a config edit or a full pipeline simply rotates to a fresh pipeline, which becomes the next calls' append target. No call ever fails because appending was not possible.

Under append mode, completion is per request, not per pipeline: the run.completed webhook fires when *this request's own documents* finish (its structured_data and records[] are scoped to them, and the payload carries the request's run_id), and GET /v1/run/:id reports status and progress for the request's documents only. The shared pipeline fires no pipeline-level run.* webhook of its own. Per-document document.structured deliveries are unaffected.

Append mode changes only how runs target pipelines — extraction, resolution, validation, and delivery behave identically. Set it once on the Spec's Source stage for connector-style integrations that push files continuously; use the per-call pipeline_mode override to force a fresh pipeline (e.g. a clean monthly table) without touching the Spec.

curl

Response

Response fields (202)

run_iduuidThe run request id — the polling handle.
spec_iduuidThe Spec being run.
statusstringAlways processing on acceptance.
input_countintegerTotal inputs accepted (files + file_urls).
poll_urlstringRelative URL to poll: /v1/run/:id.
documents[]arrayOne entry per input, in input order.
documents[].document_iduuidThe canonical document id — the id output rows (webhook records[], data products) carry for this document.
documents[].filenamestring | nullThe uploaded filename (file input), or the acquired document's filename (file_url input).
documents[].size_bytesinteger | nullFile size in bytes.
documents[].sourcestring"file" or "file_url" — which input kind produced this document.
documents[].deduplicatedbooleantrue when this upload content-matched an existing document; document_id then points at that existing (canonical) document.
documents[].linked_document_iduuidPresent only when deduplicated is true — the id of the thin link row created for this specific upload.
documents[].metadataobjectThis input's EFFECTIVE metadata for this request — the call-level metadata merged with its file_metadata entry (per-file wins). Present only when set. Under byte-identical dedup this can differ from GET /v1/documents/:id's metadata, which keeps the canonical document's tags from its first submission — this echo is the authoritative source for what THIS call tagged.

Response (202)

{
  "run_id": "a8716d18-978d-4d19-8ca5-8b3784ca857c",
  "spec_id": "1fc7807e-e1aa-4504-b796-5709986e78ed",
  "status": "processing",
  "input_count": 2,
  "poll_url": "/v1/run/a8716d18-978d-4d19-8ca5-8b3784ca857c",
  "documents": [
    {
      "document_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
      "filename": "invoice-0847.pdf",
      "size_bytes": 184320,
      "source": "file",
      "deduplicated": false,
      "metadata": { "source_system": "sap", "priority": 1, "cost_center": "AP-14" }
    },
    {
      "document_id": "9f1e2d3c-4b5a-6789-0123-456789abcdef",
      "filename": "msa-2026.pdf",
      "size_bytes": 92160,
      "source": "file_url",
      "deduplicated": true,
      "linked_document_id": "b7a6c5d4-e3f2-1098-7654-3210fedcba98"
    }
  ]
}
For push-style consumption, subscribe to the run.completed / run.failed webhooks (POST /v1/webhooks), or configure a delivery binding on the document.structured signal to receive each document's structured fields as its own webhook payload the moment it finishes.

Errors

Error responses

400missing_spec_id / no_input / invalid_metadataspec_id absent or malformed, no file or file_url supplied, metadata is not a valid flat JSON object, or file_metadata fails validation (a bad per-file bag, the merged bag exceeding the caps, or a key that matches no file/file_url — the error names the offending key).
401unauthorizedMissing or invalid API key.
402insufficient_creditsCredit balance too low for the ingest work — no documents are ingested and no run is created.
413file_too_largeA file exceeds your tier's per-file size cap.
429rate_limit_exceededDaily submission quota for the run namespace exhausted.