Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.retab.com/llms.txt

Use this file to discover all available pages before exploring further.

Overview

When a workflow reaches a reviewable block — a block with config.review whose predicate flagged its output for review — the run pauses and creates a review. The review is attached to one (run_id, block_id) pair. It is the object you read and mutate to walk from “the model produced this” to “a human approved, corrected, or rejected it, and the run resumed.” Everything is driven through client.workflows.reviews.*.
Reviews replace the legacy v1 decision endpoints. Those have been removed — client.workflows.reviews.* is the review surface.

Review Shape

A review carries immutable output versions keyed by content hash, plus at most one terminal decision:
FieldWhat it holds
versions_by_idImmutable snapshots of the gated block’s output, keyed by sha256(canonical_json(snapshot)).
decisionThe terminal verdict, or null while the block is still awaiting review.
triggered_byThe review predicate that opened the gate.
awaiting_sinceWhen the review started waiting.
Every actor — whether a model, a managed agent, or a human — is described by the same symmetric Actor shape:
{
    "kind": "human",          # "model" | "agent" | "human"
    "id": "user_42",
    "display_name": "Dana Rivera",
}
This means versions_by_id[...].author and decision.decided_by are the same type — you never special-case “was this a person or a model.”

The review loop

1. Find the work

List the queue to find reviews awaiting a decision. Filter by workflow_id when you only want one workflow’s queue.
queue = client.workflows.reviews.list(workflow_id="wf_abc123", limit=50)

for item in queue.data:
    print(item.workflow_run_id, item.block_id, item.awaiting_since)

2. Inspect the output

Read the full review and choose the exact version to decide on. The model’s original output is the version whose origin is model_output; corrective edits are additional entries in versions_by_id.
review = client.workflows.reviews.get("run_abc123", "block_extract")

model_output_version_id, model_output_version = next(
    (version_id, version)
    for version_id, version in review.versions_by_id.items()
    if version.origin == "model_output"
)
print(model_output_version.snapshot)

3. Correct if needed

When the model’s output is close but wrong, append a full corrected snapshot. The returned review includes the new content-hash version_id, which you can then approve.
review = client.workflows.reviews.get("run_abc123", "block_extract")

edited = client.workflows.reviews.edit(
    "run_abc123",
    "block_extract",
    parent_id=model_output_version_id,
    snapshot={"total_amount": 325, "vendor_name": "Acme Corp"},
    origin="human_edit",
    note="Corrected total.",
)

version_id = next(
    version_id
    for version_id, version in edited.versions_by_id.items()
    if version.snapshot == {"total_amount": 325, "vendor_name": "Acme Corp"}
)
The server validates the snapshot against the gated block’s canonical review shape and stores the exact object you submit when it is valid:
Block typeSnapshot shape
extractThe raw extracted JSON object, for example { "invoice_number": "INV-991", "total": 1200 }.
classifierOnly the selected category: { "category": "booking_confirmation" }. Do not include reasoning.
splitA document manifest: { "documents": [{ "name": "legal_mentions", "pages": [1, 2] }] }.
for_eachA partition manifest: { "partitions": [{ "key": "INV-001", "pages": [1, 2] }] }. Only split_by_key for-each reviews are reviewable.
For split and for-each snapshots, pages must be positive integers, sorted, and unique within each item. The append call returns 422 if the snapshot does not match the block type. It does not patch, normalize, or merge partial edits.

4. Decide

There are two verdicts:
VerdictEffect on the run
approvedThe selected version_id flows downstream and the run resumes.
rejectedThe run is cancelled. A reason is required.
# Approve the exact version you inspected or just appended.
client.workflows.reviews.approve(
    "run_abc123",
    "block_extract",
    version_id=version_id,
)

# Reject the exact version you inspected.
client.workflows.reviews.reject(
    "run_abc123",
    "block_extract",
    version_id=model_output_version_id,
    reason="Source document is illegible; cannot verify totals.",
)
If another reviewer already decided the same review, the server returns 409. Re-read the review and use its decision as the source of truth. Repeated approval/rejection submissions for the exact same final decision return submission_status="already_applied" with the current review. A different terminal decision after the review is already decided returns 409.

Version rules

  • version_id is computed as sha256(canonical_json(snapshot)).
  • OutputVersion does not store its own id; the id is the key in versions_by_id.
  • parent_id means “this edit was authored from this version.” It does not mean “latest.”
  • Re-submitting the same snapshot with the same parent_id is idempotent and returns the existing version.
  • Re-submitting the same snapshot with a different parent_id is rejected with 409.
  • After decision is set, the review is terminal: no more versions can be appended and no second decision can be created.

Waiting for a gate

If your integration submits a run and needs to wait until a block is ready for review, use wait_for. It polls until the review exists and has no terminal decision.
run = client.workflows.runs.create(workflow_id="wf_abc123", ...)

review = client.workflows.reviews.wait_for(
    run.id,
    "block_extract",
    timeout=120.0,
    poll_interval=2.0,
)

print("review is awaiting a decision:", review.block_id)

API reference

EndpointMethod
List Reviewsreviews.list(...)
Get Reviewreviews.get(run_id, block_id)
Append Versionreviews.edit(...)
Submit Decisionreviews.approve / reject(...)