Quickstart

Send your first document through Holofin and read the structured result. Four steps, one endpoint, about five minutes.

Before you start

You need two things, both from the dashboard:

1

An API key

Keys are workspace-scoped and stay active even if the person who created them leaves.

Create an API key
2

A workflow

Its ID looks like wf_abc123 and appears in the workflow's overview page.

Open your workflows

There is no separate upload, parse or extract call. A workflow does the whole pipeline, so this quickstart only ever touches /workflow-runs/.

1. Send a document

Documents travel base64-encoded in the JSON body. Build the body in a file, so the command works on macOS and Linux and for files of any size. The call returns immediately with a run_id; processing continues in the background.

Create a workflow run

base64 < statement.pdf | tr -d '\n' \
  | jq -Rs '{workflow_id: "wf_abc123", document_name: "statement.pdf", document_b64: .}' \
  > body.json

curl -X POST https://app.holofin.ai/api/v1.1/workflow-runs/ \
  -H "X-API-Key: $HOLOFIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

202 Accepted

{
  "run_id": "wr_abc123xyz789",
  "workflow_id": "wf_abc123",
  "workflow_name": "Statement intake",
  "document_id": "doc_def456uvw",
  "extraction_id": null,
  "document_name": "statement.pdf",
  "status": "pending",
  "created_at": "2026-01-15T10:30:00Z",
  "metadata": {}
}

Pass a metadata object to carry your own reference through to every response and webhook. To file the document under a dossier, add case_id, see Dossiers. Field values for the dossier go through POST /cases/, never through a run.

2. Wait for the result

Two ways to learn the run has finished. Use webhooks in production; polling is fine while you are getting started.

Webhooks (recommended)

Register an endpoint once and Holofin posts a signed workflow-run.completed event when the run lands, along with workflow-run.failed and workflow-run.paused. Set webhook_url on a single run to override your endpoints for that run only, which is handy when testing against a tunnel.

Set up webhooks

Polling

Fetch the run until status reaches a terminal value: completed, failed or cancelled. The two non-terminal values are pending and running, plus paused when the workflow is waiting on a human reviewer.

Poll until the run is terminal

RUN_ID=wr_abc123xyz789

while :; do
  STATUS=$(curl -s "https://app.holofin.ai/api/v1.1/workflow-runs/$RUN_ID" \
    -H "X-API-Key: $HOLOFIN_API_KEY" | jq -r .status)
  echo "$STATUS"
  # paused is not terminal: a reviewer will resume the run
  case "$STATUS" in completed|failed|cancelled) break ;; esac
  sleep 5
done

3. Read the result

A completed run carries the aggregated output of every node that ran. The two fields you will use most:

FieldWhat it holds
extraction_resultA document_parts array, one entry per logical document found in the file, each with its part_type, pages, validation_status and extracted data. null when no extractor node completed.
fraud_analysisThe forensic verdict: risk_level, risk_score, risk_factors and trust_factors. null when no fraud analyzer node ran.

One PDF can hold several documents. Holofin segments it, so document_parts is an array even for a single-document file. Read the first part with jq '.extraction_result.document_parts[0].data'.

The full result structure

4. Handle the unhappy path

Before you ship, decide what each of these does in your integration:

CaseWhat you get
The run failsstatus: "failed" and a workflow-run.failed webhook. Partial results from nodes that did complete are still on the run.
The workflow needs a humanworkflow-run.paused, then workflow-run.confirmed once a reviewer decides. A paused run is not a failed one.
Extraction is unreliableA part with validation_status of WARNING or ERROR. The run still completes, so check the part, not just the run.
You are out of credits402 Payment Required on the create call. See Errors.

Where to go next