> ## Documentation Index
> Fetch the complete documentation index at: https://docs.extend.ai/llms.txt
> Use this file to discover all available pages before exploring further.
>
> ## API version
> The current API version is `2026-02-09`, served at the site root (no version prefix in URLs).
> If this page URL contains `/2025-04-21/` or `/2024-12-23/`, you are reading an older API version.
> Prefer the current docs at https://docs.extend.ai/llms.txt unless the user explicitly needs that older version.
> Do not treat older-version pages as the source of truth for new integrations.

> Complete reference for configuring Extend workflows via the API — the steps array, every step type, routing rules, and the create, deploy, and run lifecycle.

## Configuring Workflows

A workflow's behavior is defined by its `steps` array. You set it when you [create a workflow](/api-reference/endpoints/workflow/create-workflow), [update its draft](/api-reference/endpoints/workflow/update-workflow), or [create a version](/api-reference/endpoints/workflow/create-workflow-version). This page is the complete reference for every step type and routing rule. For the end-to-end create → deploy → run lifecycle, start with [Create a Workflow](/workflows/overview).

Each step has a `name`, a `type`, an optional `config`, and a `next` array that defines where documents flow after the step completes. The same shape is used in request bodies and in the workflow version responses returned by the API.

## Quick Start

The simplest useful workflow extracts structured data from a document:

```
trigger → parse → extract → review
```

```json
{
  "name": "Invoice Processing",
  "steps": [
    {
      "name": "trigger",
      "type": "TRIGGER",
      "next": [{ "step": "parse" }]
    },
    {
      "name": "parse",
      "type": "PARSE",
      "next": [{ "step": "extract" }]
    },
    {
      "name": "extract",
      "type": "EXTRACT",
      "config": {
        "extractor": { "id": "ex_abc123", "version": "latest" }
      },
      "next": [{ "step": "review" }]
    },
    {
      "name": "review",
      "type": "HUMAN_REVIEW"
    }
  ]
}
```

Every workflow starts with a `TRIGGER` step followed by a `PARSE` step. After parsing, you can chain any combination of processing, branching, and validation steps.

## Key Concepts

### Routing

Each step's `next` array defines where documents flow. For most step types, you only need to specify the target `step`:

```json
"next": [{ "step": "extract" }]
```

For branching step types, each `next` entry includes a routing field specific to the step type:

```json
// CLASSIFY or SPLIT — use classificationId
"next": [
  { "step": "extract_invoice", "classificationId": "cls_invoice" },
  { "step": "extract_receipt", "classificationId": "cls_receipt" }
]

// CONDITIONAL — use conditionId
"next": [
  { "step": "review", "conditionId": "high_value" },
  { "step": "webhook", "conditionId": "default_path" }
]

// RULE_VALIDATION — use result
"next": [
  { "step": "webhook", "result": "pass" },
  { "step": "review", "result": "fail" }
]
```

### Saved processors vs. inline configs

`EXTRACT`, `CLASSIFY`, and `SPLIT` steps specify their processor in one of two ways — provide exactly one per step:

* **Saved reference** — `extractor` / `classifier` / `splitter`: points at a saved processor by ID and version.

  ```json
  "config": { "extractor": { "id": "ex_abc123", "version": "latest" } }
  ```

* **Inline config** — `extractorConfig` / `classifierConfig` / `splitterConfig`: embeds the full processor configuration directly in the step, using the same config shape as the standalone run endpoints (e.g. [Create Extract Run](/api-reference/endpoints/extract/create-extract-run)). No saved processor is needed.

  ```json
  "config": {
    "extractorConfig": {
      "schema": {
        "type": "object",
        "properties": {
          "invoice_number": { "type": "string" },
          "total": { "type": "number" }
        }
      },
      "extractionRules": "Prefer the remit-to address."
    }
  }
  ```

Inline configs make a workflow definition **self-contained and portable**: it carries no workspace-specific processor IDs, so the same file validates and deploys against any workspace. This is especially useful for [GitHub-managed workflow files](/workflows/github-app-integration) and for provisioning workflows programmatically across environments.

A few things to know about inline configs:

* Inline extractor configs **require `schema`** — schema-less extraction (schema inferred from the file at run time) is a run-endpoint feature and is not supported in workflows.
* Inline configs have no `version`; the config embedded in the deployed workflow version is exactly what runs. Responses return the inline config verbatim.
* `CONDITIONAL_EXTRACT` rules only support saved references.

You can mix the two styles freely within one workflow — for example, an inline classifier routing to extract steps that reference saved extractors.

## Workflow Patterns

### Linear Extraction

The simplest pattern — every step has exactly one downstream step.

```
trigger → parse → extract → webhook
```

```json
[
  { "name": "trigger", "type": "TRIGGER", "next": [{ "step": "parse" }] },
  { "name": "parse", "type": "PARSE", "next": [{ "step": "extract" }] },
  {
    "name": "extract",
    "type": "EXTRACT",
    "config": { "extractor": { "id": "ex_abc123", "version": "latest" } },
    "next": [{ "step": "webhook" }]
  },
  { "name": "webhook", "type": "WEBHOOK_RESPONSE" }
]
```

### Classify and Route

Use a `CLASSIFY` step to route documents to different extractors based on document type. Each `next` entry's `classificationId` must match a classification **ID** from the classifier's config.

```
                         ┌─ cls_invoice ─→ extract_invoice ─┐
trigger → parse → classify─ cls_receipt ─→ extract_receipt ─┼→ review → webhook
                         └─ cls_other ──→───────────────────┘
```

First, your classifier defines classifications with stable IDs:

```typescript
const classifierConfig = {
  classifications: [
    { id: "cls_invoice", type: "invoice", description: "Invoice documents" },
    { id: "cls_receipt", type: "receipt", description: "Receipt documents" },
    { id: "cls_other", type: "other", description: "Other documents" },
  ],
};
```

Then the workflow step uses those IDs as `classificationId` values:

```json
[
  { "name": "trigger", "type": "TRIGGER", "next": [{ "step": "parse" }] },
  { "name": "parse", "type": "PARSE", "next": [{ "step": "classify" }] },
  {
    "name": "classify",
    "type": "CLASSIFY",
    "config": {
      "classifier": { "id": "cl_abc123", "version": "0.1" }
    },
    "next": [
      { "step": "extract_invoice", "classificationId": "cls_invoice" },
      { "step": "extract_receipt", "classificationId": "cls_receipt" },
      { "step": "review", "classificationId": "cls_other" }
    ]
  },
  {
    "name": "extract_invoice",
    "type": "EXTRACT",
    "config": { "extractor": { "id": "ex_invoice456", "version": "1.0" } },
    "next": [{ "step": "review" }]
  },
  {
    "name": "extract_receipt",
    "type": "EXTRACT",
    "config": { "extractor": { "id": "ex_receipt789", "version": "1.0" } },
    "next": [{ "step": "review" }]
  },
  { "name": "review", "type": "HUMAN_REVIEW", "next": [{ "step": "webhook" }] },
  { "name": "webhook", "type": "WEBHOOK_RESPONSE" }
]
```

Conditions use classification **IDs** (e.g. `"cls_invoice"`), not type strings (e.g. `"invoice"`). IDs are stable across renames — if you rename a classification type from `"invoice"` to `"billing_invoice"`, the ID stays the same and routing continues to work.

### Split and Route

Use a `SPLIT` step to break a multi-document file into individual sub-documents and route each one by type. The same ID-based routing rules apply as for `CLASSIFY`.

```
                      ┌─ cls_invoice ─→ extract_invoice ─┐
trigger → parse → split─ cls_receipt ─→ extract_receipt ─┼→ collect → webhook
                      └─ cls_other ──→ review ───────────┘
```

```json
[
  { "name": "trigger", "type": "TRIGGER", "next": [{ "step": "parse" }] },
  { "name": "parse", "type": "PARSE", "next": [{ "step": "split" }] },
  {
    "name": "split",
    "type": "SPLIT",
    "config": {
      "splitter": { "id": "spl_abc123", "version": "0.1" }
    },
    "next": [
      { "step": "extract_invoice", "classificationId": "cls_invoice" },
      { "step": "extract_receipt", "classificationId": "cls_receipt" },
      { "step": "review", "classificationId": "cls_other" }
    ]
  },
  {
    "name": "extract_invoice",
    "type": "EXTRACT",
    "config": { "extractor": { "id": "ex_invoice456", "version": "1.0" } },
    "next": [{ "step": "collect" }]
  },
  {
    "name": "extract_receipt",
    "type": "EXTRACT",
    "config": { "extractor": { "id": "ex_receipt789", "version": "1.0" } },
    "next": [{ "step": "collect" }]
  },
  { "name": "review", "type": "HUMAN_REVIEW", "next": [{ "step": "collect" }] },
  { "name": "collect", "type": "COLLECT", "next": [{ "step": "webhook" }] },
  { "name": "webhook", "type": "WEBHOOK_RESPONSE" }
]
```

### Conditional Logic

Use a `CONDITIONAL` step to route based on extracted data values. Each condition has an `id` that is referenced by `next[].conditionId`.

```
trigger → parse → extract → route_total ─┬─ high_value ──→ review → webhook
                                         └─ default_path → webhook
```

```json
[
  { "name": "trigger", "type": "TRIGGER", "next": [{ "step": "parse" }] },
  { "name": "parse", "type": "PARSE", "next": [{ "step": "extract" }] },
  {
    "name": "extract",
    "type": "EXTRACT",
    "config": { "extractor": { "id": "ex_abc123", "version": "latest" } },
    "next": [{ "step": "route_total" }]
  },
  {
    "name": "route_total",
    "type": "CONDITIONAL",
    "config": {
      "conditions": [
        {
          "id": "high_value",
          "type": "IF",
          "operation": "GTE",
          "leftOperand": "{{ extract.output.value.total }}",
          "rightOperand": "10000"
        },
        { "id": "default_path", "type": "ELSE" }
      ]
    },
    "next": [
      { "step": "review", "conditionId": "high_value" },
      { "step": "webhook", "conditionId": "default_path" }
    ]
  },
  { "name": "review", "type": "HUMAN_REVIEW", "next": [{ "step": "webhook" }] },
  { "name": "webhook", "type": "WEBHOOK_RESPONSE" }
]
```

Reference an upstream step's output in `leftOperand` (and `rightOperand`, when comparing to another value) using the `{{ stepName.output.value.field }}` template syntax — `stepName` is the producing step's `name`, `.output.value` is the extractor's value payload, and `.field` is the field to compare. `operation` accepts `EQUALS`, `GTE`, `LTE`, `IS_NULL`, `CONTAINS`, or `NO_OP`. See [Conditional Steps](/workflows/workflow-steps/conditional-steps) for the full reference syntax.

### Validation

Use `RULE_VALIDATION` to check extracted data against business rules and branch on the result.

```
trigger → parse → extract → validate ─┬─ pass → webhook
                                      └─ fail → review → webhook
```

```json
[
  { "name": "trigger", "type": "TRIGGER", "next": [{ "step": "parse" }] },
  { "name": "parse", "type": "PARSE", "next": [{ "step": "extract" }] },
  {
    "name": "extract",
    "type": "EXTRACT",
    "config": { "extractor": { "id": "ex_abc123", "version": "latest" } },
    "next": [{ "step": "validate" }]
  },
  {
    "name": "validate",
    "type": "RULE_VALIDATION",
    "config": {
      "rules": [
        {
          "name": "total_matches_sum",
          "formula": "extraction1.total = extraction1.subtotal + extraction1.tax",
          "description": "Checks invoice math"
        }
      ]
    },
    "next": [
      { "step": "webhook", "result": "pass" },
      { "step": "review", "result": "fail" }
    ]
  },
  { "name": "review", "type": "HUMAN_REVIEW", "next": [{ "step": "webhook" }] },
  { "name": "webhook", "type": "WEBHOOK_RESPONSE" }
]
```

See [Formulas](/workflows/formulas) for the rule expression language and [Validation Step](/workflows/workflow-steps/validation-step) for the UI guide.

## Step Reference

All saved processor references (`extractor`, `classifier`, `splitter`) require an explicit `version` field. Inline configs (`extractorConfig`, `classifierConfig`, `splitterConfig`) have no version — see [Saved processors vs. inline configs](#saved-processors-vs-inline-configs).

`CLASSIFY` and `SPLIT` steps do not support `"latest"` — you must pin to a specific semver version (e.g. `"0.1"`) or `"draft"`. This is because classification IDs used for routing are tied to a specific version's config. If a new version is published with different classifications, routing would break silently.

| Step type             | `"latest"` | `"draft"` | Semver (e.g. `"1.0"`) | Inline config |
| --------------------- | ---------- | --------- | --------------------- | ------------- |
| `EXTRACT`             | Yes        | Yes       | Yes                   | Yes           |
| `CONDITIONAL_EXTRACT` | Yes        | Yes       | Yes                   | **No**        |
| `CLASSIFY`            | **No**     | Yes       | Yes                   | Yes           |
| `SPLIT`               | **No**     | Yes       | Yes                   | Yes           |

### Trigger

The single entry point for every workflow. Must route to exactly one `PARSE` step.

```json
{ "name": "trigger", "type": "TRIGGER", "next": [{ "step": "parse" }] }
```

### Parse

Converts the uploaded file into structured content (OCR, text extraction). Must appear immediately after the trigger.

Optionally configure parsing behavior with `parseConfig`. See [Parse Configuration Options](/parsing/configuration).

```json
{
  "name": "parse",
  "type": "PARSE",
  "config": {
    "parseConfig": {
      "target": "markdown",
      "chunkingStrategy": { "type": "page" }
    }
  },
  "next": [{ "step": "extract" }]
}
```

### Extract

Extracts structured data from parsed content. Specify the extractor with a saved reference (`extractor` — version required: `"latest"`, `"draft"`, or semver) or an inline config (`extractorConfig`). Can be created without `config` — `next` cannot be set until `config` is provided, and `config` is required before deploy.

```json
{
  "name": "extract",
  "type": "EXTRACT",
  "config": {
    "extractor": { "id": "ex_abc123", "version": "latest" }
  },
  "next": [{ "step": "review" }]
}
```

Or with an inline config — note that `schema` is required for inline extractor configs:

```json
{
  "name": "extract",
  "type": "EXTRACT",
  "config": {
    "extractorConfig": {
      "schema": {
        "type": "object",
        "properties": {
          "invoice_number": { "type": "string" },
          "total": { "type": "number" }
        }
      }
    }
  },
  "next": [{ "step": "review" }]
}
```

### Classify

Routes documents to different downstream steps based on classification. Conditions must reference classification **IDs**, not type strings. Specify the classifier with a saved reference (`classifier` — requires a pinned version; `"latest"` is not allowed) or an inline config (`classifierConfig`). Can be created without `config` — `next` cannot be set until `config` is provided, and `config` is required before deploy.

See the [Classify and Route pattern](#classify-and-route) above for a complete example with a saved reference. With an inline config, the `next[].classificationId` values must match the `id` values in the inline `classifications` array:

```json
{
  "name": "classify",
  "type": "CLASSIFY",
  "config": {
    "classifierConfig": {
      "classifications": [
        { "id": "cls_invoice", "type": "invoice", "description": "Invoice documents" },
        { "id": "cls_other", "type": "other", "description": "Anything else" }
      ]
    }
  },
  "next": [
    { "step": "extract_invoice", "classificationId": "cls_invoice" },
    { "step": "review", "classificationId": "cls_other" }
  ]
}
```

### Split

Splits a multi-document file into sub-documents and routes each one. Same ID-based routing rules as `CLASSIFY`. Specify the splitter with a saved reference (`splitter` — requires a pinned version; `"latest"` is not allowed) or an inline config (`splitterConfig`, with routing IDs coming from its `splitClassifications` array). Can be created without `config` — `next` cannot be set until `config` is provided, and `config` is required before deploy.

See the [Split and Route pattern](#split-and-route) above for a complete example.

### Merge Extract

Combines outputs from multiple upstream extract steps. Use `mergeOrder` to control how overlapping fields are prioritized.

```json
{
  "name": "merge",
  "type": "MERGE_EXTRACT",
  "config": { "mergeOrder": "confidence" },
  "next": [{ "step": "webhook" }]
}
```

### Conditional

Routes based on extracted data values using if/else logic. See the [Conditional Logic pattern](#conditional-logic) above.

For the UI-based version of this step, see [Conditional Steps](/workflows/workflow-steps/conditional-steps).

### Conditional Extract

Chooses which extractor to run based on formula conditions. Each rule pairs a formula with an extractor reference — rules only support saved references, not inline configs. The last rule must have `formula: "TRUE"` as a default catch-all to prevent runtime failures when no other rule matches. Can be created without `config` — `next` cannot be set until `config` is provided, and `config` is required before deploy.

```json
{
  "name": "route_extractor",
  "type": "CONDITIONAL_EXTRACT",
  "config": {
    "rules": [
      {
        "name": "cigna_provider",
        "formula": "metadata.provider_name = \"cigna\"",
        "extractor": { "id": "ex_cigna", "version": "latest" }
      },
      {
        "name": "fallback",
        "formula": "TRUE",
        "extractor": { "id": "ex_generic", "version": "latest" }
      }
    ]
  },
  "next": [{ "step": "validate" }]
}
```

See [Formulas](/workflows/formulas) for the expression language and [Conditional Extraction Step](/workflows/workflow-steps/conditional-extraction-step) for the UI guide.

### Rule Validation

Checks extracted data against boolean rules. Can be created without `config` — `next` cannot be set until `config` is provided, and `config` is required before deploy. See the [Validation pattern](#validation) above for a complete example.

### External Data Validation

Sends extraction data to an external HTTP endpoint for validation. Can be created without `config` — `next` cannot be set until `config` is provided, and `config` is required before deploy.

```json
{
  "name": "external_validate",
  "type": "EXTERNAL_DATA_VALIDATION",
  "config": {
    "requestOptions": {
      "url": "https://api.example.com/validate",
      "method": "POST",
      "headers": { "x-api-key": "secret" },
      "contentType": "application/json"
    },
    "failureBehavior": "EXIT"
  },
  "next": [{ "step": "review" }]
}
```

See [External Data Validation Step](/workflows/workflow-steps/external-data-validation-step) for more context.

### Human Review

Pauses the workflow for manual review in the dashboard before continuing to downstream steps.

```json
{ "name": "review", "type": "HUMAN_REVIEW", "next": [{ "step": "webhook" }] }
```

### Collect

Joins multiple upstream branches before continuing. Use after `CLASSIFY` or `SPLIT` branches to wait for all parallel work to complete.

```json
{ "name": "collect", "type": "COLLECT", "next": [{ "step": "webhook" }] }
```

A Collect step is also what makes a package run extract across every file at once. See [Multifile Extraction in Workflows](/workflows/multifile-extraction).

### File Conversion

Converts the file format before downstream processing. Use `failureBehavior` to control whether conversion failures stop the workflow.

```json
{
  "name": "convert",
  "type": "FILE_CONVERSION",
  "config": { "failureBehavior": "CONTINUE" },
  "next": [{ "step": "parse" }]
}
```

### Webhook Response

Terminal step that delivers results to your webhook endpoint. Must not have `next`.

```json
{ "name": "webhook", "type": "WEBHOOK_RESPONSE" }
```

## Next steps

#### [Create a Workflow](/workflows/overview)

The end-to-end create → deploy → run lifecycle.

#### [Workflow Versioning](/workflows/workflow-versioning)

Deploy, pin, and promote workflow versions.

#### [Reviewing Workflow Runs](/workflows/reviewing-workflow-run)

Handle steps that pause for human review.

#### [Create Workflow Version API](/api-reference/endpoints/workflow/create-workflow-version)

Full request and response schema.