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.

What are Workflows?

Workflows are visual, block-based pipelines that let you chain together multiple document processing operations. Instead of writing code for each step, you can drag and drop blocks onto a canvas, connect them, and create powerful document automation flows. A workflow typically consists of:
  • Input blocks - Entry points for data:
    • Document - Upload files (PDF, images, Word, Excel)
    • JSON Input - Pass structured JSON data
  • Processing blocks - Operations like Extract, Parse, Split, Classifier
  • Logic blocks - Conditional flows like review gates, Function, If/Else routing, and API Call

Tests

Workflow tests validate individual block outputs with saved inputs and assertions. Use them to check that an Extract, Function, Split, or Classifier block still behaves as expected after you change schemas, prompts, code, or block configuration. Learn more in Tests.

Experiments

Experiments measure a block’s consistency by replaying the same Extract, Split, Classifier, or split-by-key For Each block over a fixed document set with multiple consensus passes. Use them to compare block configurations, find low-agreement documents or fields, and decide what needs stricter tests. Learn more in Experiments.

Creating a Workflow

  1. Navigate to the Workflows section in your dashboard
  2. Click Create Workflow to open a new canvas
  3. Drag blocks from the sidebar onto the canvas
  4. Connect blocks by dragging from output handles to input handles
  5. Configure each block by clicking on it
  6. Your workflow auto-saves as you build

Connecting Blocks

Blocks communicate through handles that define the type of data they accept or produce:
Handle TypeIconDescription
File📎Document files (PDF, images, Word, Excel)
JSON{ }Structured data extracted from documents

Connection Rules

  • File → File: Pass documents between processing blocks
  • JSON → JSON: Pass extracted data between logic blocks
  • Each input handle accepts only one connection
  • Connections validate automatically to prevent incompatible links

Declarative Workflow Spec

You can also define and manage workflows from YAML. A declarative spec uses apiVersion: workflows.retab.com/v1alpha2 and keeps topology in spec.edges. Every edge endpoint is explicit: it names the block and the raw runtime handle.
apiVersion: workflows.retab.com/v1alpha2
kind: Workflow
metadata:
  id: wf_abc123
  name: Invoice Workflow

spec:
  blocks:
    start_document-node:
      type: start_document
      label: Input Document

    extract-node:
      type: extract
      label: Extract Fields
      config:
        inputs:
          - name: source_doc
            type: file
            is_primary: true
        json_schema:
          type: object
          properties: {}

  edges:
    - from:
        block: start_document-node
        handle: output-file-0
      to:
        block: extract-node
        handle: input-file-source_doc
The SDK exposes the spec lifecycle under client.workflows.spec:
validation = client.workflows.spec.validate(yaml_definition)
plan = client.workflows.spec.plan(yaml_definition)
result = client.workflows.spec.apply(yaml_definition)
exported = client.workflows.spec.get(result.workflow_id)
Use validate() for parse and handle checks, plan() to preview draft changes, apply() to reconcile the draft workflow, and get() to get canonical YAML from an existing workflow. plan() and apply() return Terraform-style summary, resource_changes, and rendered_plan fields so clients can inspect exactly what changed. Call client.workflows.publish(workflow_id) separately when the draft should become the live published workflow. For endpoint details, see:

Edit Mode vs Run Mode

Workflows have two operational modes:

Edit Mode

  • Add, remove, and configure blocks
  • Create and delete connections
  • Rename the workflow
  • View generated Python code

Run Mode

  • Upload documents to input blocks
  • Execute the workflow step-by-step
  • View results at each stage
  • Download processed files and extracted data
Toggle between modes using the switch at the top of the canvas.

Running a Workflow

A workflow is fundamentally an asynchronous job. When you start it, Retab creates a workflow run, executes each step on the server, and stores the results on that run. You can then poll the run until it finishes and inspect the stored step outputs. For the SDK and HTTP endpoint details, see the workflow API reference:

From the Dashboard

  1. Switch to Run Mode
  2. Upload a document to each Document input block
  3. Click Run Workflow
  4. Watch as each block processes (status indicators show progress)
  5. Click on output handles to view results

Using the SDK

The Python, Node, and Go SDKs expose workflow metadata, graph authoring, run execution, and typed step inspection:
  • client.workflows.* / client.Workflows.* for list(), get(), create(), update(), delete(), and publish()
  • client.workflows.blocks.* / client.Workflows.Blocks.* and client.workflows.edges.* / client.Workflows.Edges.* for programmatic graph changes
  • client.workflows.runs.* / client.Workflows.Runs.* and client.workflows.steps.* / client.Workflows.Steps.* for running flows and reading results

Discover input block IDs

Workflow run inputs are keyed by the IDs of your start_document and start_json blocks. List the workflow’s blocks to discover them.
from retab import Retab

client = Retab()

blocks = client.workflows.blocks.list("wf_abc123")

document_start_id = next(block.id for block in blocks.data if block.type == "start_document")
json_start_id = next(block.id for block in blocks.data if block.type == "start_json")

Run and wait for completion

Workflows support two input maps:
  • documents for Document (start_document) blocks
  • json_inputs for JSON Input (start_json) blocks
import time
from pathlib import Path

from retab import Retab

client = Retab()

workflow = client.workflows.get("wf_abc123")
blocks = client.workflows.blocks.list(workflow.id)
document_start_id = next(block.id for block in blocks.data if block.type == "start_document")
json_start_id = next(block.id for block in blocks.data if block.type == "start_json")

run = client.workflows.runs.create(
    workflow_id=workflow.id,
    documents={
        document_start_id: Path("path/to/invoice.pdf"),
    },
    json_inputs={
        json_start_id: {"customer_id": "cust_123", "priority": "high"},
    },
)

terminal_statuses = {"completed", "error", "cancelled"}
while run.lifecycle.status not in terminal_statuses and run.lifecycle.status != "awaiting_review":
    time.sleep(1)
    run = client.workflows.runs.get(run.id)

print(run.lifecycle.status)
if run.lifecycle.status == "awaiting_review":
    print(run.lifecycle.waiting_for_block_ids)
elif run.lifecycle.status == "error":
    raise RuntimeError(run.lifecycle.message)
elif run.lifecycle.status == "cancelled":
    raise RuntimeError(run.lifecycle.reason or "Workflow run was cancelled")
else:
    for step_summary in client.workflows.steps.list(run.id):
        step = client.workflows.steps.get(step_summary.step_id)
        if step.handle_outputs:
            print(step.block_id, step.handle_outputs)

steps.list(run.id) returns the step roster for a run. For the full execution record for one block, including typed inputs and outputs, use steps.get(run.id, block_id).

Inspect step outputs

Start with steps.list(run.id) when you need the blocks that ran. Then call steps.get(run.id, block_id) for the specific execution record you want to inspect. Step payloads are normalized into HandlePayload objects. For JSON-producing blocks, extracted_data is shorthand for the default output-json-0 handle.
# Step roster:
for step in client.workflows.steps.list(run.id):
    print(step.block_id, step.lifecycle.status)
    if step.artifact:
        print(step.artifact.operation, step.artifact.id)

# Full execution record for one step:

step = client.workflows.steps.get(run.id, "extract-block-id")
print(step.lifecycle.status)
if step.extracted_data:
print(step.extracted_data)

Use steps.list(run.id, block_ids=[...]) when you only need a subset of step summaries. Use steps.get(run.id, block_id) when you need the normalized execution record for a single block.

Fetch the artifact record

Some blocks persist a durable artifact record. step.artifact is only the stable pointer:
{ "operation": "conditional_evaluation", "id": "ceval_abc123" }
Use client.workflows.artifacts.get(step.artifact) to dereference that pointer. The response is the backing record flattened with operation at the top level, so consumers can dispatch on one object without juggling an extra record wrapper.
step = client.workflows.steps.get(run.id, "conditional-block-id")
if step.artifact:
    artifact = client.workflows.artifacts.get(step.artifact)
    print(artifact.operation)
    print(artifact.matched_condition_ids)
    print(artifact.evaluations)
workflows.artifacts.list(run.id) dereferences every artifact produced by a run. Pass operation= or block_id= when you only need a subset.
condition_records = client.workflows.artifacts.list(
    run.id,
    operation="conditional_evaluation",
)
operationproduced byrecord includes
extractionextractextraction result, choices, likelihoods, schema details
splitsplitsplit result and output document grouping
classificationclassifierselected class and consensus details
parseparseparsed document content
editeditedited document result
partitionfor_each_sentinel_startpartitioned items for the loop
conditional_evaluationconditionalevaluations, selected_handles, matched_condition_ids
while_loop_terminationwhile_looptermination reason and final condition evaluations
api_call_invocationapi_callrequest/response attempts, retry trace, and final error
function_invocationfunctionfunction inputs, output, duration, and final error

Build workflows from code

The same SDK can create and publish workflow graphs:
workflow = client.workflows.create(name="Invoice Pipeline")
blocks = client.workflows.blocks.list(workflow.id)
start_document_block = next(block for block in blocks.data if block.type == "start_document")

extract_block = client.workflows.blocks.create(
workflow.id,
id="extract-invoice",
type="extract",
label="Extract Invoice",
position_x=320,
position_y=0,
config={
"json_schema": {
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"total_amount": {"type": "number"},
},
},
},
)

client.workflows.edges.create(
workflow.id,
id="edge-start-to-extract",
source_block=start_document_block.id,
target_block=extract_block.id,
source_handle="output-file-0",
target_handle="input-file-0",
)

client.workflows.publish(workflow.id, description="Initial version")

Use client.workflows.list() or client.workflows.get(workflow_id) when you need to browse existing workflows before launching a run.

Reading Workflow Results

The standard production pattern is to run the workflow, keep the returned run.id, and poll the run until lifecycle.status reaches completed, error, cancelled, or awaiting_review.
  1. Start the workflow from the SDK or API
  2. Receive a run.id and an initial lifecycle immediately
  3. Poll the workflow run until it finishes or waits for review
  4. Read the step results from the completed run
The workflow run is the source of truth for execution state and outputs. This is enough for many scripts, internal tools, and backend services.

Workflow Execution Order

Workflows execute in topological order based on the block connections:
  1. Start from Document input blocks
  2. Process each block once all its inputs are ready
  3. Continue until all blocks are processed or an error occurs
  4. Read outputs from the completed run and its step results
If a block fails, execution stops and the error is displayed on that block.

Conditional Routing

When using Classifier or If/Else blocks, only the branches that receive data are executed. Blocks on skipped branches are marked as “skipped” rather than failed.

Viewing Generated Code

Every workflow can be exported as Python code. Click View Code in the sidebar to see the equivalent SDK calls for your workflow. This is useful for:
  • Integrating workflows into your existing codebase
  • Running workflows in production environments
  • Understanding how the visual blocks translate to API calls

Best Practices

Begin with a single Extract or Parse block, then gradually add complexity. Test each addition before moving on.
Rename blocks to describe their purpose (e.g., “Invoice Data” instead of “Extract 1”). This makes complex workflows easier to understand.
Use Note blocks to document sections of your workflow. They don’t affect execution but help explain the logic.
For critical data, add a review gate to the extraction block. This ensures a reviewer checks low-likelihood results before they proceed.
When processing different document types, use a Classifier block to route each document to the appropriate extraction schema.
Before deploying, run your workflow with representative sample documents to catch edge cases.

Example: Invoice Processing Workflow

Here’s a common workflow pattern for processing invoices:
  1. Start block accepts the invoice PDF
  2. Extract block pulls out vendor, amount, date, line items
  3. The extract block’s review gate flags low-likelihood extractions for review
  4. Read the verified data from the completed workflow run

Example: Multi-Document Classification Workflow

For workflows that process mixed document bundles:
  1. Classifier routes documents by category (Invoice, Contract, Receipt)
  2. Each Extract block uses a document-specific schema
  3. Function blocks compute derived fields for each document type
  4. Merge JSON combines results from all branches into a single output