> ## 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.

# Blocks

> Complete reference for supported workflow block types

## Overview

Workflow blocks are the building blocks of document processing pipelines. Each block has specific inputs, outputs, and configuration options.

## Block Categories

Blocks are organized into three categories:

| Category  | Purpose                                    |
| --------- | ------------------------------------------ |
| **Core**  | Workflow entry points and utilities        |
| **Tools** | Document processing operations             |
| **Logic** | Conditional flows and data transformations |

***

## Core Blocks

### Document (Start)

<Card title="Document" icon="paperclip" color="#22c55e">
  The entry point for your workflow. Upload documents here for processing.
</Card>

**Outputs:** File

**Configuration:** None (just upload your document in Run mode)

**Supported Formats:**

* PDF documents
* Images (PNG, JPG, JPEG, GIF, WebP, TIFF, BMP)
* Microsoft Word (.docx, .doc)
* Microsoft Excel (.xlsx, .xls)
* Microsoft PowerPoint (.pptx, .ppt)

**Usage:**

* Drag multiple Document blocks for workflows that combine multiple inputs
* Each Document block can receive one file per workflow run
* Files are automatically converted to PDF when required by downstream blocks

***

### JSON Input (Start)

<Card title="JSON Input" icon="braces" color="#8b5cf6">
  Entry point for structured JSON data. Define a schema and pass JSON data when
  running the workflow.
</Card>

**Outputs:** JSON

**Configuration:**

| Setting         | Description                                 |
| --------------- | ------------------------------------------- |
| **JSON Schema** | Define the structure of expected input data |

**Schema Example:**

```json theme={null}
{
  "type": "object",
  "properties": {
    "customer_id": { "type": "string" },
    "priority": { "type": "string", "enum": ["low", "medium", "high"] },
    "metadata": {
      "type": "object",
      "properties": {
        "source": { "type": "string" }
      }
    }
  },
  "required": ["customer_id"]
}
```

**Usage:**

* Use JSON Input blocks to pass structured data into workflows without documents
* Combine with Document blocks to enrich extractions with external data
* Connect to Extract blocks as additional context for AI-powered extraction
* Use with Function or If/Else blocks for data-driven workflow logic

**SDK Example:**

<CodeGroup>
  ```python Python theme={null}
  run = client.workflows.runs.create(
      workflow_id="wf_abc123",
      json_inputs={
          "json-block-id": {"customer_id": "cust_123", "priority": "high"}
      }
  )
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"
  	"log"

  	retab "github.com/retab-dev/retab/clients/go"
  )

  func main() {
  	ctx := context.Background()
  	client, err := retab.NewClient("")
  	if err != nil {
  		log.Fatal(err)
  	}

  	run, err := client.Workflows.Runs.Create(ctx, &retab.WorkflowRunsCreateParams{
  		WorkflowID: "wf_abc123",
  		JSONInputs: &map[string]any{
  			"json-block-id": map[string]any{
  				"customer_id": "cust_123",
  				"priority":    "high",
  			},
  		},
  	})
  	if err != nil {
  		log.Fatal(err)
  	}
  	log.Println(run.ID)
  }
  ```

  ```ruby Ruby theme={null}
  require 'retab'

  client = Retab::Client.new(api_key: ENV['RETAB_API_KEY'])

  run = client.workflows.runs.create(
    workflow_id: 'wf_abc123',
    json_inputs: {
      'json-block-id' => { customer_id: 'cust_123', priority: 'high' },
    },
  )

  puts run.id
  ```

  ```php PHP theme={null}
  <?php
  $run = $client->workflows()->runs()->create(
      workflowId: 'wf_abc123',
      jsonInputs: [
          'json-block-id' => ['customer_id' => 'cust_123', 'priority' => 'high'],
      ],
  );
  ```

  ```csharp C# theme={null}
  using System;
  using System.Collections.Generic;
  using System.Threading.Tasks;
  using Retab;
  using RetabClient = Retab.Retab;

  var client = new RetabClient("YOUR_API_KEY");

  // CreateAsync triggers a new workflow run; per-input documents/json maps are
  // supplied through the request body emitted by the SDK.
  var run = await client.Workflows.Runs.CreateAsync(
      new WorkflowRunsCreateOptions
      {
          WorkflowId = "wf_abc123",
          JsonInputs = new Dictionary<string, object>
          {
              ["json-block-id"] = new Dictionary<string, object>
              {
                  ["customer_id"] = "cust_123",
                  ["priority"] = "high",
              },
          },
      }
  );

  Console.WriteLine(run.Id);
  ```

  ```typescript TypeScript theme={null}
  const run = await client.workflows.runs.create(
    "wf_abc123",
    undefined,
    {
      "json-block-id": { customer_id: "cust_123", priority: "high" },
    },
  );

  console.log(run.id);
  ```

  ```rust Rust theme={null}
  use retab::{models::CreateWorkflowRunRequest, resources::workflow_runs, Retab};
  use std::collections::HashMap;

  let client = Retab::new(std::env::var("RETAB_API_KEY")?);
  let mut request = CreateWorkflowRunRequest::new("wf_abc123");
  request.json_inputs = Some(HashMap::from([(
      "json-block-id".to_string(),
      serde_json::json!({ "customer_id": "cust_123", "priority": "high" }),
  )]));
  let run = client
      .workflows().runs()
      .create(workflow_runs::CreateParams::new(request.into()))
      .await?;

  println!("{}", run.id);
  ```

  ```java Java theme={null}
  import com.retab.RetabClient;

  public final class Example {
    public static void main(String[] args) throws Exception {
      RetabClient client = new RetabClient(System.getenv("RETAB_API_KEY"));

      var result = client.workflows().runs().create("wf_abc123", null, null, null, null);
      System.out.println(result);
    }
  }
  ```
</CodeGroup>

### Note

<Card title="Note" icon="sticky-note" color="#eab308">
  Add comments and documentation to your workflow.
</Card>

**Inputs:** None\
**Outputs:** None

Notes don't affect workflow execution—they're purely for documentation. Use them to:

* Explain complex logic
* Document configuration choices
* Leave instructions for teammates

***

## Tool Blocks

### Extract

<Card title="Extract" icon="layers" color="#8b5cf6">
  Extract structured data from documents using a JSON schema.
</Card>

**Inputs:** File, plus optional additional inputs (JSON, File)
**Outputs:** JSON (extracted data)

**Configuration:**

| Setting               | Description                              | Default       |
| --------------------- | ---------------------------------------- | ------------- |
| **Schema**            | JSON Schema defining fields to extract   | `{}`          |
| **Model**             | AI model for extraction                  | `retab-small` |
| **Consensus**         | Number of parallel extractions (1-10)    | `1`           |
| **Additional Inputs** | Named inputs for context (JSON or files) | `[]`          |

**Schema Example:**

```json theme={null}
{
  "type": "object",
  "properties": {
    "invoice_number": { "type": "string" },
    "total_amount": { "type": "number" },
    "vendor": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "address": { "type": "string" }
      }
    }
  }
}
```

**Consensus Mode:**\
When `n_consensus > 1`, the block runs multiple extractions in parallel and returns the consensus data on `output-json-0`. Field likelihoods are retained as consensus metadata for review and inspection, not as workflow output handles.

**Additional Inputs:**

You can add named input handles to provide extra context during extraction:

* **JSON inputs**: Structured data from other blocks
* **File inputs**: Additional reference documents

***

### Parse

<Card title="Parse" icon="scan-text" color="#06b6d4">
  Parse documents into machine-usable JSON with a `Pages` array.
</Card>

**Inputs:** File\
**Outputs:** JSON (`{"Pages":[...]}`)

**Configuration:**

| Setting   | Description          | Default       |
| --------- | -------------------- | ------------- |
| **Model** | AI model for parsing | `retab-small` |

**Use Cases:**

* Generate page-by-page JSON content for indexing/RAG pipelines
* Add parsed page context to Extract as additional JSON input
* Convert scanned PDFs to machine-usable page content

***

### Split

<Card title="Split" icon="scissors" color="#f59e0b">
  Split multi-page documents into separate PDFs by subdocument.
</Card>

**Inputs:** File (PDF)\
**Outputs:** Multiple File outputs (one per subdocument)

**Configuration:**

| Setting          | Description                                               |
| ---------------- | --------------------------------------------------------- |
| **Subdocuments** | List of document subdocuments with names and descriptions |
| **Model**        | AI model for classification                               |

**Subdocument Example:**

```json theme={null}
{
  "subdocuments": [
    {
      "name": "Invoice",
      "description": "Pages containing invoice details, line items, and totals"
    },
    {
      "name": "Contract",
      "description": "Legal agreement pages with terms and signatures"
    },
    {
      "name": "Supporting Documents",
      "description": "Receipts, certificates, and other attachments"
    }
  ]
}
```

Each category creates a separate output handle. Connect downstream blocks to process each category differently.

<Note>
  Non-PDF documents are automatically converted to PDF before splitting.
</Note>

***

### Classifier

<Card title="Classifier" icon="tags" color="#14b8a6">
  Classify documents into one of the predefined categories.
</Card>

**Inputs:** File\
**Outputs:** Multiple File outputs (one per category, only the matched category receives the document)

**Configuration:**

| Setting        | Description                                             |
| -------------- | ------------------------------------------------------- |
| **Categories** | List of document categories with names and descriptions |
| **Model**      | AI model for classification                             |

**Difference from Split:**

| Feature      | Split                                     | Classifier                           |
| ------------ | ----------------------------------------- | ------------------------------------ |
| **Input**    | Multi-page document                       | Single document                      |
| **Output**   | Multiple PDFs (pages grouped by category) | Same document routed to one category |
| **Use Case** | Separating bundled documents              | Routing different document types     |

**Example:**

```json theme={null}
{
  "categories": [
    {
      "name": "Invoice",
      "description": "Documents containing billing information and payment requests"
    },
    {
      "name": "Receipt",
      "description": "Proof of payment or purchase confirmation"
    },
    {
      "name": "Contract",
      "description": "Legal agreements and binding documents"
    }
  ]
}
```

The AI analyzes the document and routes it to exactly one category. Downstream blocks connected to other categories are skipped.

***

### Edit

<Card title="Edit" icon="pen-square" color="#10b981">
  Fill PDF forms using AI with JSON instructions or pre-defined templates.
</Card>

**Inputs:**

* File (document to edit) — *only when "Use Template" is off*
* JSON (instructions/data)

**Outputs:** File (filled document)

**Configuration:**

| Setting          | Description                                                       |
| ---------------- | ----------------------------------------------------------------- |
| **Model**        | AI model to use for filling                                       |
| **Use Template** | Toggle to use a pre-defined template instead of an input document |
| **Template**     | Template to use (shown when "Use Template" is on)                 |

**Two Operating Modes:**

1. **Document Mode** (default): Edit an input document using AI
   * Connect a document from another block (e.g., Start, Split)
   * Provide instructions via the JSON input
   * The AI fills the form fields based on your instructions

2. **Template Mode**: Fill a pre-defined template
   * Enable "Use Template" toggle
   * Select a template from your template library
   * No document input required — the template provides the form structure
   * Provide data/instructions via the JSON input

**Example Instructions (JSON):**

```json theme={null}
{
  "name": "Acme Corp",
  "address": "12 Main St",
  "notes": "Leave signature fields blank"
}
```

The JSON input provides instructions/data for filling. Connect this to an Extract block's JSON output for end-to-end automation.

***

## Logic Blocks

### Review Gates

<Card title="Review Gate" icon="user-check" color="#ec4899">
  Pause a gated block for review and approval.
</Card>

Review is not a separate workflow block. Configure `review`
on a block that supports review, such as `extract`, `split`,
`classifier`, or `for_each` with `map_method: "split_by_key"`. When the gate
predicate fires, that same block enters `awaiting_review` and a
[review](/workflows/Reviews) is created with an opaque review id.

**Configuration:** `config.review`

**How It Works:**

1. Add `review` to the block config.
2. Choose a predicate such as `always`, `any_required_field_null`, or `field_confidence_lt`.
3. When the predicate fires, the block pauses at `awaiting_review`.
4. A reviewer sees the block output alongside the source document.
5. The reviewer can approve the inspected `version_id`, append a corrected version with `parent_version_id`, or reject through Reviews.
6. After approval, the selected `versions[decision.version_id].snapshot` continues through the workflow.

**Use Cases:**

* Validate critical extractions before sending to downstream systems
* Quality control for high-value documents
* Compliance requirements that mandate human oversight

**Example extract gate:**

```json theme={null}
{
  "model": "retab-small",
  "inputs": [{ "name": "document", "type": "file", "is_primary": true }],
  "json_schema": {
    "type": "object",
    "required": ["invoice_number", "total"],
    "properties": {
      "invoice_number": { "type": "string" },
      "total": { "type": "number" }
    }
  },
  "review": {
    "predicate": {
      "kind": "any_required_field_null"
    }
  }
}
```

Common extract predicates include `always`, `validation_failed`,
`confidence_lt`, `any_required_field_null`, and `field_confidence_lt`.
For `split` and split-by-key `for_each` gates, use split-style predicates such
as `split_count_neq`, `any_split_pages_lt`, and `boundary_confidence_lt`.

<Note>
  The review object is actor-neutral: a model output, an agent-created
  correction, and a human-created correction all use the same `versions`
  and `decision.version_id` shape. There is no separate agent-review
  configuration on `config.review`; automated reviewers should create
  correction versions through `client.workflows.reviews.versions.create(...)`
  and then approve or reject through `client.workflows.reviews.*`.
</Note>

***

### Function

<Card title="Function" icon="code" color="#6366f1">
  Execute function code in a sandboxed environment for transformations and
  validations. The `language` field currently supports `Python`.
</Card>

**Inputs:** JSON
**Outputs:** JSON (with computed/transformed fields)

**Configuration:**

| Setting              | Description                                                          |
| -------------------- | -------------------------------------------------------------------- |
| **language**         | Execution language. Currently only `Python` is supported             |
| **output\_schema**   | JSON schema defining the output structure                            |
| **code**             | Python code with a `transform(input_data: Input) -> Output` function |
| **timeout\_seconds** | Sandbox execution timeout (1--300, default 60)                       |

**Example:**

```python theme={null}
from models import Input, Output

def transform(input_data: Input) -> Output:
    subtotal = sum(item.amount for item in input_data.line_items)
    tax = subtotal * 0.1
    total = subtotal + tax
    return Output(subtotal=subtotal, tax=tax, total=total)
```

**Available packages:** pydantic, pandas, numpy, scipy, duckdb, rapidfuzz, beautifulsoup4, lxml, python-dateutil.

<Note>
  Outbound network access is disabled. Use the `api_call` block for HTTP
  requests.
</Note>

See [Functions Documentation](/workflows/Functions) for the complete reference.

***

**Use Cases:**

* Restructure flat data into nested objects for downstream systems
* Select only the fields you need from a large extraction
* Rename fields to match your API or database schema
* Prepare data for downstream APIs or databases that expect a specific format

<Note>
  Unmapped fields are excluded from the output. If an input path doesn't exist,
  the mapping is skipped without error.
</Note>

***

### If / Else

<Card title="If / Else" icon="network" color="#f97316">
  Route data to different branches based on conditions.
</Card>

**Inputs:** JSON\
**Outputs:** Multiple JSON outputs (one per branch: If, Else If, Else)

**Configuration:**

| Setting        | Description                                              |
| -------------- | -------------------------------------------------------- |
| **Conditions** | List of conditions to evaluate in order                  |
| **Has Else**   | Whether to include a default else branch (default: true) |

**Condition Structure:**

Each condition can have multiple sub-conditions combined with AND/OR:

```json theme={null}
{
  "conditions": [
    {
      "branch_name": "if",
      "sub_conditions": [
        {
          "path": "data.total_amount",
          "operator": "is_greater_than",
          "value": 1000
        },
        {
          "path": "data.vendor.country",
          "operator": "is_equal_to",
          "value": "US"
        }
      ],
      "logical_operator": "and"
    }
  ]
}
```

**Available Operators:**

| Type           | Operators                                                                                    |
| -------------- | -------------------------------------------------------------------------------------------- |
| **Existence**  | `exists`, `does_not_exist`, `is_empty`, `is_not_empty`                                       |
| **Comparison** | `is_equal_to`, `is_not_equal_to`                                                             |
| **String**     | `contains`, `starts_with`, `ends_with`, `matches_regex`                                      |
| **Number**     | `is_greater_than`, `is_less_than`, `is_greater_than_or_equal_to`, `is_less_than_or_equal_to` |
| **Boolean**    | `is_true`, `is_false`                                                                        |
| **Array**      | `length_equal_to`, `length_greater_than`, `length_less_than`                                 |
| **Date**       | `is_after`, `is_before`, `is_after_or_equal_to`, `is_before_or_equal_to`                     |

**How It Works:**

1. Conditions are evaluated in order (If, Else If 1, Else If 2, ...)
2. The first matching condition determines the output branch
3. Data is routed to exactly one branch
4. If no conditions match and `has_else` is true, data goes to the Else branch
5. Downstream blocks on non-matched branches are skipped

**Example Use Cases:**

* Route high-value invoices for additional approval
* Process documents differently based on vendor country
* Flag incomplete extractions for review

***

### API Call

<Card title="API Call" icon="globe" color="#3b82f6">
  Make HTTP requests to external APIs and use the response in your workflow.
</Card>

**Inputs:** JSON (optional request body or parameters)
**Outputs:** JSON (API response)

**Configuration:**

| Setting            | Description                                                          | Default  |
| ------------------ | -------------------------------------------------------------------- | -------- |
| **URL**            | The API endpoint URL                                                 | Required |
| **Method**         | HTTP method (GET, POST, PUT, PATCH, DELETE)                          | `POST`   |
| **Headers**        | Custom HTTP headers (e.g., authentication)                           | `{}`     |
| **Request Schema** | JSON schema used to select request-body fields from the input JSON   | Optional |
| **Field Mappings** | Optional target-field to source-path mapping before schema filtering | `{}`     |
| **Output Schema**  | JSON schema describing the response for downstream workflow blocks   | Optional |

**Example Configuration:**

```json theme={null}
{
  "url": "https://api.example.com/validate",
  "method": "POST",
  "headers": {
    "Authorization": "Bearer ${API_KEY}",
    "Content-Type": "application/json"
  },
  "field_mappings": {
    "invoice_number": "data.invoice_number",
    "amount": "data.total_amount"
  },
  "request_schema": {
    "type": "object",
    "properties": {
      "invoice_number": {"type": "string"},
      "amount": {"type": "number"}
    }
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "valid": {"type": "boolean"}
    }
  }
}
```

**Use Cases:**

* Validate extracted data against external systems
* Enrich documents with data from your CRM or ERP
* Trigger actions in third-party services based on extraction results
* Look up additional information using extracted identifiers

For POST, PUT, and PATCH requests, the block sends the full input JSON as the
request body by default. Add `field_mappings` to rename or derive top-level
request fields from dot paths in the input JSON, then use `request_schema` to
keep only the fields the external API expects.

<Note>
  API Call blocks execute synchronously. For long-running operations, prefer
  async endpoints or job-based integrations on the external system.
</Note>

***

### Merge JSON

<Card title="Merge JSON" icon="combine" color="#8b5cf6">
  Combine multiple JSON objects into a single structured object.
</Card>

**Inputs:** Multiple JSON inputs (configurable)\
**Outputs:** JSON (merged object)

**Configuration:**

| Setting    | Description                                 |
| ---------- | ------------------------------------------- |
| **Inputs** | Named input slots for JSON objects to merge |

**Example Configuration:**

```json theme={null}
{
  "inputs": [
    { "name": "invoice_data" },
    { "name": "vendor_data" },
    { "name": "line_items" }
  ]
}
```

**Output Structure:**

The merged output wraps each input under its named key:

```json theme={null}
{
  "invoice_data": { ... extracted invoice fields ... },
  "vendor_data": { ... extracted vendor fields ... },
  "line_items": { ... extracted line items ... }
}
```

This is useful for:

* Combining extractions from multiple documents
* Aggregating data from parallel processing branches
* Creating comprehensive outputs from Split or Classifier workflows

***

### While Loop

<Card title="While Loop" icon="refresh-cw" color="#64748b">
  Repeat contained blocks until a termination condition is met.
</Card>

**Inputs:** JSON or File
**Outputs:** JSON or File (final iteration result)

**Configuration:**

| Setting                    | Description                                            | Default |
| -------------------------- | ------------------------------------------------------ | ------- |
| **Max Iterations**         | Maximum number of loop iterations                      | `10`    |
| **Termination Conditions** | Conditions that stop the loop when matched             | `[]`    |
| **Loop Context Template**  | Text template with loop variables for contained blocks | `""`    |

**How It Works:**

1. Data enters the loop through the left input handle
2. Contained blocks inside the loop process the data
3. After each iteration, termination conditions are evaluated
4. If a condition matches (or max iterations reached), the loop exits
5. Final output is passed through the right output handle

**Termination Conditions:**

Configure conditions using the same operators as If/Else:

| Type           | Operators                                                           |
| -------------- | ------------------------------------------------------------------- |
| **Comparison** | `is_equal_to`, `is_not_equal_to`, `is_greater_than`, `is_less_than` |
| **Existence**  | `exists`, `does_not_exist`, `is_empty`, `is_not_empty`              |
| **Boolean**    | `is_true`, `is_false`                                               |
| **Array**      | `length_equal_to`, `length_greater_than`, `length_less_than`        |

**Example Configuration:**

```json theme={null}
{
  "max_iterations": 5,
  "termination_conditions": [
    {
      "path": "data.status",
      "operator": "is_equal_to",
      "value": "complete"
    }
  ]
}
```

**Loop Context Variables:**

The loop context template can include these variables:

* `{{iteration_number}}` - Current iteration (e.g., "1 of 10")
* `{{termination_condition_breakdown}}` - Detailed evaluation of why the loop continued
* `{{data}}` - JSON data from the previous iteration

**Use Cases:**

* Iteratively refine extractions until quality thresholds are met
* Process documents until a specific field value is detected
* Implement retry logic with conditional exit

<Note>
  The loop always runs at least once. Termination conditions are evaluated after
  each iteration completes.
</Note>

***

### For Each

<Card title="For Each" icon="layers" color="#f97316">
  Process items in parallel and combine results.
</Card>

**Inputs:** JSON (array) or File (PDF)
**Outputs:** JSON (combined results)

**Configuration:**

| Setting             | Description                                              | Default         |
| ------------------- | -------------------------------------------------------- | --------------- |
| **Map Method**      | How to split input into items                            | `split_by_page` |
| **Allow Overlap**   | For `split_by_key`, whether keyed chunks may share pages | `false`         |
| **Max Iterations**  | Maximum items to process                                 | `100`           |
| **Reduce Strategy** | How to combine iteration outputs                         | `concat_array`  |

**Map Methods:**

| Method          | Description                                    | Input Type |
| --------------- | ---------------------------------------------- | ---------- |
| `iterate_array` | Process each item in a JSON array              | JSON       |
| `split_by_page` | Split PDF into N-page chunks                   | File       |
| `split_by_key`  | Split PDF by semantic boundaries (AI-detected) | File       |

**Reduce Strategies:**

| Strategy       | Description                              | Output Type |
| -------------- | ---------------------------------------- | ----------- |
| `concat_array` | Combine all outputs into an array        | JSON        |
| `first`        | Return only the first iteration's output | JSON        |
| `last`         | Return only the last iteration's output  | JSON        |
| `none`         | No output (side effects only)            | None        |

**Example: Iterate Array**

Process each line item in an invoice separately:

```json theme={null}
{
  "map_method": "iterate_array",
  "map_source_path": "data.line_items",
  "reduce_strategy": "concat_array",
  "reduce_concat_data_key": "processed_items"
}
```

**Example: Split by Page**

Process a multi-page document one page at a time:

```json theme={null}
{
  "map_method": "split_by_page",
  "split_page_range": 1,
  "reduce_strategy": "concat_array"
}
```

**Example: Split by Key**

Split a document containing multiple invoices by invoice number:

```json theme={null}
{
  "map_method": "split_by_key",
  "key": "invoice_number",
  "instructions": "Invoice number found at the top of each invoice",
  "allow_overlap": true,
  "reduce_strategy": "concat_array",
  "reduce_concat_item_key": "invoice_id"
}
```

Set `"allow_overlap": false` only when keyed partitions must be exclusive.
The default `true` allows multiple keyed partitions to include the same page.

Add map-phase review to inspect the proposed split boundaries before the
for-each body runs:

```json theme={null}
{
  "map_method": "split_by_key",
  "key": "invoice_number",
  "instructions": "Invoice number found at the top of each invoice",
  "review": {
    "predicate": {
      "kind": "boundary_confidence_lt",
      "threshold": 0.8
    }
  }
}
```

**Iteration Context:**

Inside the loop, contained blocks can access:

* `{{key}}` - The partition key for the current item (page range or semantic key)
* `{{input_json}}` - The full input JSON (for `iterate_array` method)

**Use Cases:**

* Extract data from each page of a multi-page document independently
* Process bundled documents that contain multiple invoices/receipts
* Apply different logic to each item in an array
* Batch process documents with parallel execution

<Note>
  For Each processes items sequentially. The reduce phase runs after all
  iterations complete. Use `split_by_key` when documents have multiple logical
  sections that need AI to identify boundaries.
</Note>

***

## Block I/O Types

Understanding input/output types helps you connect blocks correctly:

| Type     | Color          | Description             |
| -------- | -------------- | ----------------------- |
| **File** | Blue (📎)      | Documents, PDFs, images |
| **JSON** | Purple (`{ }`) | Structured data objects |

***

## Tips for Building Workflows

<AccordionGroup>
  <Accordion title="Start with the output in mind">
    Identify what data you need at the end, then work backwards to determine which blocks you need.
  </Accordion>

  <Accordion title="Add Function blocks for calculations">
    Instead of computing values after reading workflow results elsewhere, use Function blocks to add totals, percentages, and derived fields directly in the workflow.
  </Accordion>

  <Accordion title="Use Classifier for routing">
    When handling different document types, use Classifier to route each document to the appropriate extraction schema before processing.
  </Accordion>

  <Accordion title="Split before specialized processing">
    When handling mixed document bundles (e.g., invoice + contract in one PDF), use Split first, then apply specific Extract schemas to each subdocument.
  </Accordion>

  <Accordion title="Combine results with Merge JSON">
    When processing multiple documents or branches, use Merge JSON to combine all extracted data into a single structured output.
  </Accordion>

  <Accordion title="Use If/Else for conditional logic">
    Route data based on extracted values—for example, send high-value invoices to a different API endpoint or flag certain conditions for review.
  </Accordion>

  <Accordion title="Use For Each for multi-document PDFs">
    When a single PDF contains multiple logical documents (like several invoices),
    use For Each with `split_by_key` to process each one independently with its
    own extraction.
  </Accordion>

  <Accordion title="Use While Loop for iterative refinement">
    When you need to retry or refine processing until a quality threshold is met,
    use While Loop with termination conditions that check for the desired outcome.
  </Accordion>

  <Accordion title="Test edge cases">
    Run your workflow with documents that have missing fields, poor scan quality, or unusual formats to ensure robust handling.
  </Accordion>
</AccordionGroup>
