Skip to main content
An eval is one graded run of a workflow against a target version: it re-extracts every document in scope, compares each result to that document’s ground truth, and freezes an accuracy headline you can trust to compare against other runs. The Health → Evaluations screen drives this from the app, where a run can be scoped to the whole dataset or a single sub-dataset. This recipe drives the same loop over the v3 API — which always evaluates the whole dataset — so you can wire it into CI, a nightly job, or a regression gate. The loop is four calls:
  1. Upload files + ground truth into the workflow’s dataset — POST /v3/workflows/{workflow_id}/dataset/upload/.
  2. Launch the eval — POST /v3/workflows/{workflow_id}/evals/ — and keep the returned eval_id.
  3. Poll the eval until its status leaves in_progressGET /v3/workflows/{workflow_id}/evals/{eval_id}/.
  4. Read the headline (accuracy, matched / mismatched / ungraded), or list prior runs to compare over time.

When to use this

  • Regression gate in CI — run an eval on every workflow change and fail the build if accuracy drops below a threshold.
  • Nightly accuracy tracking — launch a run on a cron and chart accuracy per run_number over time.
  • Version comparison — evaluate two versions on the same dataset and diff their headlines.
Throughout, set ANYFORMAT_API_KEY and a WORKFLOW_ID (the target workflow’s UUID):

Step 1 — Upload files + ground truth

Seed the dataset one document at a time. Each call uploads 1–10 files as a single document packet plus an optional ground_truth — the expected values for that document. Ground-truth keys are the workflow schema’s field persistent_ids; a scalar field maps to string | null, a table/object field to an array of row objects. Creation is all-or-nothing, so a rejected file or a bad ground-truth payload stores nothing.
Ground truth attaches to the workflow’s current version. Fields the dataset has no ground truth for aren’t scored — they land as ungraded, never a failure. See Upload to Dataset for conflict handling (on_conflict) and multi-file packets.

Step 2 — Launch the eval

Launching is async and fire-and-forget: it enqueues a fresh extraction for every dataset document, pins the realized cohort into a new eval, and returns 202 immediately with an eval_id and run_number. Grading finalizes on a worker — you’ll poll for it in the next step. Omit version_id to evaluate the current version; pass one to pin a specific version.
The public API always evaluates the whole dataset — there is no way to narrow the scope from the endpoint. The app can additionally scope a run to a single sub-dataset, but that narrowing isn’t exposed on the API yet, so an API-launched eval always covers every dataset document.
The response carries counts, never internal ids: enqueued_count documents were queued, failed_count failed to enqueue. If the dataset is empty (or every document fails to enqueue) the call returns 422 EMPTY_EVAL and no eval is created — so upload at least one document in Step 1 first.

Step 3 — Poll until grading finishes

GET .../evals/{eval_id}/ always returns 200 while the eval exists — there are no precondition errors while grading is in flight. Poll it until status is terminal:
Polling is fine for a script or CI gate. For a production integration, prefer webhooks over a poll loop.

Step 4 — Read the headline

On a processed eval, accuracy is the matched / (matched + mismatched) fraction. It stays null when the graded denominator is 0 — nothing was gradable, e.g. every field was ungraded for lack of ground truth — so treat null accuracy as “no signal”, not “zero”.
Response (200 OK — processed)
To compare over time, list the workflow’s evals newest-first — each item is the same headline, so a fresh page can mix graded and still-running runs:

End-to-end

The whole loop as one runnable script — upload a folder of documents with their ground truth, launch, poll, and print the headline:

Notes & gotchas

  • Every launch costs credits. Launching runs a fresh extraction per document; re-launching runs the whole cohort again. Metering follows How credits work.
  • Idempotency replays, it doesn’t dedupe by content. Without an Idempotency-Key every launch creates a new eval. Supplying the header replays: a retry with the same key returns the original eval instead of a second run. See Idempotency.
  • Pin a version for stable replays. When you omit version_id, a replayed key sent after a new version was published resolves to a different version and returns 422. Pass an explicit version_id when you need the replay to survive a version change.
  • Empty datasets fail fast. A launch with nothing to enqueue returns 422 EMPTY_EVAL and creates no eval — upload at least one document first.
  • Each run is immutable. Editing ground truth or refining the workflow never changes a past eval — always launch a fresh run to measure a change. That’s what makes the delta between two runs real signal.

Next steps

Launch Eval

Full launch contract — target version, idempotency, error codes.

Get Eval

The status model and frozen-headline response in detail.

List Evals

Keyset pagination over a workflow’s run history.

Evaluations in the app

Drive the same loop from Health → Evaluations, with per-file and per-field drill-down.