> This page is for version v2026-02-09 (default).
> For other versions, use one of these documentation indexes:
> - v2026-02-09 (default): https://docs.extend.ai/2026-02-09/llms.txt
> - v2025-04-21: https://docs.extend.ai/2025-04-21/llms.txt
> - v2024-12-23: https://docs.extend.ai/2024-12-23/llms.txt

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

# Workflows Overview

> Create, configure, deploy, and run a document-processing workflow with the Extend API. A visual builder is also available in Extend Studio.

A **Workflow** orchestrates multiple processors into a single document pipeline — for example, classify a document, route it to the right extractor, validate the result, then deliver it to your webhook. Workflows are async-only and run end to end on each file you submit.

This guide walks through the full lifecycle via the API: **create → configure steps → deploy → run**. Prefer a visual canvas? See [Prefer the dashboard?](#prefer-the-dashboard) at the end.

## 1. Create a workflow

`POST /workflows` creates a workflow and initializes an empty draft.

#### Python

```python
from extend_ai import Extend

client = Extend()

workflow = client.workflows.create(name="Invoice Processing")
print(workflow.id)  # workflow_...
```

#### TypeScript

```typescript
import { ExtendClient } from "extend-ai";

const client = new ExtendClient();

const workflow = await client.workflows.create({ name: "Invoice Processing" });
console.log(workflow.id); // workflow_...
```

#### Java

```java
import ai.extend.ExtendClient;
import ai.extend.resources.workflows.requests.WorkflowsCreateRequest;
import ai.extend.types.Workflow;

ExtendClient client = ExtendClient.builder().build();

Workflow workflow = client.workflows().create(WorkflowsCreateRequest.builder()
    .name("Invoice Processing")
    .build());

System.out.println(workflow.getId()); // workflow_...
```

#### Go

```go
package main

import (
	"context"
	"fmt"
	"log"

	extend "github.com/extend-hq/extend-go-sdk"
	client "github.com/extend-hq/extend-go-sdk/client"
)

func main() {
	c := client.NewClient()

	workflow, err := c.Workflows.Create(context.TODO(), &extend.WorkflowsCreateRequest{
		Name: "Invoice Processing",
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(workflow.ID) // workflow_...
}
```

#### cURL

```bash
curl -X POST https://api.extend.ai/workflows \
  -H "Authorization: Bearer $EXTEND_API_KEY" \
  -H "x-extend-api-version: 2026-02-09" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Invoice Processing" }'
```

## 2. Configure the steps

A workflow is a graph of steps. Each step has a `name`, a `type`, an optional `config`, and a `next` array that defines where documents flow after it completes. Every workflow begins with a `TRIGGER` step followed by a `PARSE` step.

> **Note**
>
> An `EXTRACT` step can reference a published extractor by its processor `id` and `version` (create the extractor first — see [Processors](/evaluation/processors) and the [Extraction overview](/extraction/overview)), or embed the full extractor configuration inline with `extractorConfig` — no saved extractor needed. See [Saved processors vs. inline configs](/workflows/configuring-workflows#saved-processors-vs-inline-configs).

Update the draft's step graph with `POST /workflows/{id}` (update workflow):

```json
{
  "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": "webhook" }]
    },
    { "name": "webhook", "type": "WEBHOOK_RESPONSE" }
  ]
}
```

This is the heart of building a workflow. For the full catalog of step types, routing rules (classify/split branching, conditional logic, validation), and complete patterns, see [Configuring Workflows](/workflows/configuring-workflows).

## 3. Deploy a version

Steps accumulate on the workflow's **draft**. To make the workflow runnable, publish an immutable version with `POST /workflows/{id}/versions` (create workflow version):

#### Python

```python
# Omitting `steps` deploys the current draft as-is
version = client.workflow_versions.create(
    "workflow_abc123",
    name="Initial invoice pipeline",
)
print(version.id)
```

#### TypeScript

```typescript
// Omitting `steps` deploys the current draft as-is
const version = await client.workflowVersions.create("workflow_abc123", {
  name: "Initial invoice pipeline",
});
```

#### Java

```java
import ai.extend.resources.workflowversions.requests.WorkflowVersionsCreateRequest;
import ai.extend.types.WorkflowVersion;

// Omitting steps deploys the current draft as-is
WorkflowVersion version = client.workflowVersions().create("workflow_abc123",
    WorkflowVersionsCreateRequest.builder()
        .name("Initial invoice pipeline")
        .build());
```

#### Go

```go
// Omitting Steps deploys the current draft as-is
version, err := c.WorkflowVersions.Create(context.TODO(), "workflow_abc123", &extend.WorkflowVersionsCreateRequest{
	Name: extend.String("Initial invoice pipeline"),
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(version.ID)
```

#### cURL

```bash
curl -X POST https://api.extend.ai/workflows/workflow_abc123/versions \
  -H "Authorization: Bearer $EXTEND_API_KEY" \
  -H "x-extend-api-version: 2026-02-09" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Initial invoice pipeline" }'
```

See [Workflow Versioning](/workflows/workflow-versioning) for how draft, `latest`, and pinned semver versions behave.

## 4. Run the workflow

Submit a file to a deployed workflow with `POST /workflow_runs`. Workflow runs are asynchronous — poll with the SDK helper or receive results via webhook.

Pass either a single `file` or a `package` of files — see [Package runs](#package-runs) below.

#### Python

```python
# create_and_poll waits for the run to reach a terminal state
run = client.workflow_runs.create_and_poll(
    workflow={"id": "workflow_abc123"},
    file={"url": "https://extend-public-files.s3.us-east-2.amazonaws.com/freight-invoice.pdf"},
)

print(run.status)  # PROCESSED, NEEDS_REVIEW, FAILED, ...
```

#### TypeScript

```typescript
// createAndPoll waits for the run to reach a terminal state
const run = await client.workflowRuns.createAndPoll({
  workflow: { id: "workflow_abc123" },
  file: { url: "https://extend-public-files.s3.us-east-2.amazonaws.com/freight-invoice.pdf" },
});

console.log(run.status); // PROCESSED, NEEDS_REVIEW, FAILED, ...
```

#### Java

```java
import ai.extend.resources.workflowruns.requests.WorkflowRunsCreateRequest;
import ai.extend.resources.workflowruns.types.WorkflowRunsCreateRequestFile;
import ai.extend.types.WorkflowReference;
import ai.extend.types.FileFromUrl;
import ai.extend.types.WorkflowRun;

// createAndPoll waits for the run to reach a terminal state
WorkflowRun run = client.workflowRuns().createAndPoll(WorkflowRunsCreateRequest.builder()
    .workflow(WorkflowReference.builder().id("workflow_abc123").build())
    .file(WorkflowRunsCreateRequestFile.of(FileFromUrl.builder()
        .url("https://extend-public-files.s3.us-east-2.amazonaws.com/freight-invoice.pdf")
        .build()))
    .build());

System.out.println(run.getStatus()); // PROCESSED, NEEDS_REVIEW, FAILED, ...
```

#### Go

```go
// Workflow runs are async. Create the run, then poll c.WorkflowRuns.Retrieve
// for a terminal status or receive the result via webhook.
run, err := c.WorkflowRuns.Create(context.TODO(), &extend.WorkflowRunsCreateRequest{
	Workflow: &extend.WorkflowReference{ID: "workflow_abc123"},
	File: &extend.WorkflowRunsCreateRequestFile{
		FileFromURL: &extend.FileFromURL{
			URL: "https://extend-public-files.s3.us-east-2.amazonaws.com/freight-invoice.pdf",
		},
	},
})
if err != nil {
	log.Fatal(err)
}

fmt.Println(run.Status) // PROCESSING — poll or use a webhook for the result
```

#### cURL

```bash
curl -X POST https://api.extend.ai/workflow_runs \
  -H "Authorization: Bearer $EXTEND_API_KEY" \
  -H "x-extend-api-version: 2026-02-09" \
  -H "Content-Type: application/json" \
  -d '{
    "workflow": { "id": "workflow_abc123" },
    "file": { "url": "https://extend-public-files.s3.us-east-2.amazonaws.com/freight-invoice.pdf" }
  }'
```

For high volume or long-running pipelines, prefer webhooks over polling. See [Asynchronous Processing](/general/async-processing) and [Batch processing](/general/batch-processing) for running many files at once.

### Package runs

When several documents belong together — an invoice with its bill of lading and delivery receipt, or a loan application split across separate PDFs — submit them as a **package** instead of a single file. Pass `package` in place of `file` and Extend ingests every file up front, then runs the workflow over the full set as one `WorkflowRun`.

#### Python

```python
run = client.workflow_runs.create_and_poll(
    workflow={"id": "workflow_abc123"},
    package={
        "files": [
            {"url": "https://example.com/invoice.pdf"},
            {"url": "https://example.com/bill-of-lading.pdf"},
            {"id": "file_xK9mLPqRtN3vS8wF5hB2cQ"},
        ]
    },
)

print([f.name for f in run.files])  # one entry per file
```

#### TypeScript

```typescript
const run = await client.workflowRuns.createAndPoll({
  workflow: { id: "workflow_abc123" },
  package: {
    files: [
      { url: "https://example.com/invoice.pdf" },
      { url: "https://example.com/bill-of-lading.pdf" },
      { id: "file_xK9mLPqRtN3vS8wF5hB2cQ" },
    ],
  },
});

console.log(run.files.map((f) => f.name)); // one entry per file
```

#### Java

```java
import ai.extend.types.FileFromId;
import ai.extend.types.FileFromUrl;
import ai.extend.types.WorkflowReference;
import ai.extend.types.WorkflowRun;
import ai.extend.types.WorkflowRunPackage;
import ai.extend.types.WorkflowRunPackageFilesItem;
import ai.extend.resources.workflowruns.requests.WorkflowRunsCreateRequest;
import java.util.List;

WorkflowRun run = client.workflowRuns().createAndPoll(WorkflowRunsCreateRequest.builder()
    .workflow(WorkflowReference.builder().id("workflow_abc123").build())
    .package_(WorkflowRunPackage.builder()
        .files(List.of(
            WorkflowRunPackageFilesItem.of(FileFromUrl.builder().url("https://example.com/invoice.pdf").build()),
            WorkflowRunPackageFilesItem.of(FileFromUrl.builder().url("https://example.com/bill-of-lading.pdf").build()),
            WorkflowRunPackageFilesItem.of(FileFromId.builder().id("file_xK9mLPqRtN3vS8wF5hB2cQ").build())))
        .build())
    .build());

run.getFiles().forEach(f -> System.out.println(f.getName())); // one entry per file
```

#### Go

```go
ctx := context.TODO()
run, err := c.WorkflowRuns.Create(ctx, &extend.WorkflowRunsCreateRequest{
	Workflow: &extend.WorkflowReference{ID: "workflow_abc123"},
	Package: &extend.WorkflowRunPackage{
		Files: []*extend.WorkflowRunPackageFilesItem{
			{FileFromURL: &extend.FileFromURL{URL: "https://example.com/invoice.pdf"}},
			{FileFromURL: &extend.FileFromURL{URL: "https://example.com/bill-of-lading.pdf"}},
			{FileFromID: &extend.FileFromID{ID: "file_xK9mLPqRtN3vS8wF5hB2cQ"}},
		},
	},
})
if err != nil {
	log.Fatal(err)
}

// The Go SDK has no CreateAndPoll helper; poll until the run leaves PENDING/PROCESSING.
for run.Status == extend.WorkflowRunStatusPending || run.Status == extend.WorkflowRunStatusProcessing {
	time.Sleep(2 * time.Second)
	if run, err = c.WorkflowRuns.Retrieve(ctx, run.ID, &extend.WorkflowRunsRetrieveRequest{}); err != nil {
		log.Fatal(err)
	}
}

for _, f := range run.Files {
	fmt.Println(f.Name) // one entry per file
}
```

#### cURL

```bash
curl -X POST https://api.extend.ai/workflow_runs \
  -H "Authorization: Bearer $EXTEND_API_KEY" \
  -H "x-extend-api-version: 2026-02-09" \
  -H "Content-Type: application/json" \
  -d '{
    "workflow": { "id": "workflow_abc123" },
    "package": {
      "files": [
        { "url": "https://example.com/invoice.pdf" },
        { "url": "https://example.com/bill-of-lading.pdf" },
        { "id": "file_xK9mLPqRtN3vS8wF5hB2cQ" }
      ]
    }
  }'
```

**Constraints**

* `file` and `package` are mutually exclusive — provide exactly one. Sending both, or neither, returns a `400`.
* `package.files` accepts **2–50 entries**. For a single document, use `file`.
* Each entry must be a `{ "url": ... }` or `{ "id": ... }`. Raw text and base64 inputs are not supported in packages.
* Duplicates are rejected: the same file ID cannot appear twice, and neither can the same URL. A URL and a file ID are never treated as duplicates of each other, even if they point at the same document.
* `outputs` cannot be combined with `package`. A package run produces a single merged result across all files, so there is nowhere to attach pre-supplied per-processor outputs.

**Response**

A package run returns the same `WorkflowRun` shape as a single-file run. The `files` array holds one `FileSummary` per input file, rather than the single entry a `file` run returns.

To extract across every file in the package as one corpus, the workflow needs a `COLLECT` step ahead of its extract step. See [Multifile Extraction in Workflows](/workflows/multifile-extraction).

> **Note**
>
> A package is one run over a set of documents. To process many files *independently*, use the [Batch Run Workflow](/api-reference/endpoints/workflow/batch-create-workflow-runs) endpoint instead — it creates one workflow run per file. See [Batch processing](/general/batch-processing).

## Prefer the dashboard?

[Extend Studio](https://dashboard.extend.ai/studio) provides a visual canvas for the same lifecycle — drag steps onto the canvas, connect them, and deploy with a button.

1. In the Studio, open **Workflows** and click **Create new Workflow**, then give it a name.
   ![](/_fern-img/671aa94adab06160f73f39e882b7499ab44527c6fe6382e5909d603c93ed1bc1.webp)
2. Drag step types from the **Step Drawer** onto the canvas and connect them. Select a step to configure it — for an Extraction step, pick your published processor and version.
   ![](/_fern-img/8e59f840625f9d25603e881a0405096fe0ec9a2430365a43fe03eec4698c4b20.webp)
3. Changes save automatically to the workflow **draft**.
4. Click **Deploy** in the top-right to publish a new immutable version.
   ![](/_fern-img/44c84d8eb34d3382ef6a58715cd3d9922cdd3c03a3dc6c681e77d8aa0f5da95d.webp)

Workflows built in the Studio are run the same way — via `POST /workflow_runs` as shown above.

## Next steps

#### [Configuring Workflows](/workflows/configuring-workflows)

Every step type, routing rule, and full pipeline patterns.

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

Handle runs that pause for human review.

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

Manage, pin, and promote workflow versions.

#### [Create Workflow Run API](/api-reference/endpoints/workflow/create-workflow-run)

Full request and response schema.