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

# Multifile Extraction in Workflows

> Run a single extraction across a package of 2 to 50 files inside a workflow, with validation, review, and routing applied to the combined result.

Multifile extraction is not limited to standalone `/extract_runs` calls. A workflow can accept a package of files and run one extraction across all of them, with validation, review, and routing applied to the combined result.

For the core concept, the `package` parameter, and the response shape, start with [Multifile Extraction](/extraction/multifile). This page covers what changes when you run it inside a workflow.

## How it works

A standard workflow run takes one file and processes it end to end. A package run takes 2 to 50 files and processes them as a single unit, producing one workflow run.

Inside that run, a Collect step gathers all the files, and any Extract step placed downstream of Collect performs a multifile extraction: one extraction, one output object, shared context across every file in the package.

1. You submit a package of 2 to 50 files against a workflow.
2. Extend creates one workflow run containing every file.
3. The Trigger and Parse steps run once per file. Each file is parsed independently.
4. The Collect step waits for every file to finish upstream processing, then accumulates them into a single step run.
5. Every step downstream of Collect runs in multi-file mode. An Extract step there receives all collected files at once and performs a single multifile extraction.
6. Downstream Validation, Router, Human Review, and Webhook Response steps operate on that one combined result.

```
                ┌─ Parse (file 1) ─┐
Trigger (×N) ───┼─ Parse (file 2) ─┼─→ Collect ─→ Extract ─→ Webhook
                └─ Parse (file 3) ─┘             (multifile)
```

## Configure the workflow

Add a [`COLLECT` step](/workflows/configuring-workflows#collect) between your parse step and the extract step.

### Quick start

#### Python

```python
from extend_ai import Extend

client = Extend()

workflow = client.workflows.create(
    name="Package extraction workflow",
    steps=[
        {"type": "TRIGGER", "name": "trigger", "next": [{"step": "parse"}]},
        {"type": "PARSE", "name": "parse", "next": [{"step": "collect"}]},
        {"type": "COLLECT", "name": "collect", "next": [{"step": "extract_package"}]},
        {
            "type": "EXTRACT",
            "name": "extract_package",
            "config": {"extractor": {"id": "ex_abc123", "version": "latest"}},
            "next": [{"step": "respond"}],
        },
        {"type": "WEBHOOK_RESPONSE", "name": "respond"},
    ],
)

print("Workflow:", workflow.id)
```

#### TypeScript

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

const client = new ExtendClient();

const workflow = await client.workflows.create({
  name: "Package extraction workflow",
  steps: [
    { type: "TRIGGER", name: "trigger", next: [{ step: "parse" }] },
    { type: "PARSE", name: "parse", next: [{ step: "collect" }] },
    { type: "COLLECT", name: "collect", next: [{ step: "extract_package" }] },
    {
      type: "EXTRACT",
      name: "extract_package",
      config: { extractor: { id: "ex_abc123", version: "latest" } },
      next: [{ step: "respond" }],
    },
    { type: "WEBHOOK_RESPONSE", name: "respond" },
  ],
});

console.log("Workflow:", workflow.id);
```

#### Java

```java
import ai.extend.ExtendClient;
import ai.extend.resources.workflows.requests.WorkflowsCreateRequest;
import ai.extend.types.CollectStepDefinition;
import ai.extend.types.ExtractStepDefinition;
import ai.extend.types.ExtractStepDefinitionConfig;
import ai.extend.types.ExtractorRef;
import ai.extend.types.ParseStepDefinition;
import ai.extend.types.SimpleNextEntry;
import ai.extend.types.TriggerStepDefinition;
import ai.extend.types.WebhookResponseStepDefinition;
import ai.extend.types.Workflow;
import ai.extend.types.WorkflowStepDefinition;
import java.util.List;

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

Workflow workflow = client.workflows().create(WorkflowsCreateRequest.builder()
    .name("Package extraction workflow")
    .steps(List.of(
        WorkflowStepDefinition.trigger(TriggerStepDefinition.builder()
            .name("trigger")
            .next(List.of(SimpleNextEntry.builder().step("parse").build()))
            .build()),
        WorkflowStepDefinition.parse(ParseStepDefinition.builder()
            .name("parse")
            .next(List.of(SimpleNextEntry.builder().step("collect").build()))
            .build()),
        WorkflowStepDefinition.collect(CollectStepDefinition.builder()
            .name("collect")
            .next(List.of(SimpleNextEntry.builder().step("extract_package").build()))
            .build()),
        WorkflowStepDefinition.extract(ExtractStepDefinition.builder()
            .name("extract_package")
            .config(ExtractStepDefinitionConfig.builder()
                .extractor(ExtractorRef.builder()
                    .id("ex_abc123")
                    .version("latest")
                    .build())
                .build())
            .next(List.of(SimpleNextEntry.builder().step("respond").build()))
            .build()),
        WorkflowStepDefinition.webhookResponse(WebhookResponseStepDefinition.builder()
            .name("respond")
            .build())))
    .build());

System.out.println("Workflow: " + workflow.getId());
```

#### 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: "Package extraction workflow",
		Steps: []*extend.WorkflowStepDefinition{
			{Trigger: &extend.TriggerStepDefinition{
				Name: "trigger",
				Next: []*extend.SimpleNextEntry{{Step: "parse"}},
			}},
			{Parse: &extend.ParseStepDefinition{
				Name: "parse",
				Next: []*extend.SimpleNextEntry{{Step: "collect"}},
			}},
			{Collect: &extend.CollectStepDefinition{
				Name: "collect",
				Next: []*extend.SimpleNextEntry{{Step: "extract_package"}},
			}},
			{Extract: &extend.ExtractStepDefinition{
				Name: "extract_package",
				Config: &extend.ExtractStepDefinitionConfig{
					Extractor: &extend.ExtractorRef{
						ID:      "ex_abc123",
						Version: extend.String("latest"),
					},
				},
				Next: []*extend.SimpleNextEntry{{Step: "respond"}},
			}},
			{WebhookResponse: &extend.WebhookResponseStepDefinition{
				Name: "respond",
			}},
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Workflow:", workflow.ID)
}
```

#### 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": "Package extraction workflow",
    "steps": [
      { "type": "TRIGGER", "name": "trigger", "next": [{ "step": "parse" }] },
      { "type": "PARSE", "name": "parse", "next": [{ "step": "collect" }] },
      { "type": "COLLECT", "name": "collect", "next": [{ "step": "extract_package" }] },
      {
        "type": "EXTRACT",
        "name": "extract_package",
        "config": { "extractor": { "id": "ex_abc123", "version": "latest" } },
        "next": [{ "step": "respond" }]
      },
      { "type": "WEBHOOK_RESPONSE", "name": "respond" }
    ]
  }'
```

Steps accumulate on the workflow's draft. Deploy a version before running it. See [Workflow Versioning](/workflows/workflow-versioning).

### Start a package run

Use `package` instead of `file` on `POST /workflow_runs`. The two are mutually exclusive. You can mix file URLs and file IDs in the same package.

#### Python

```python
from extend_ai import Extend

client = Extend()

# For most production use cases, we recommend webhooks instead of polling
result = client.workflow_runs.create_and_poll(
    workflow={"id": "workflow_abc123", "version": "latest"},
    package={
        "files": [
            {"url": "https://example.com/msa.pdf"},
            {"url": "https://example.com/amendment-1.pdf"},
            {"id": "file_xK9mLPqRtN3vS8wF5hB2cQ"},
        ]
    },
)

print("Workflow run:", result.status)
```

#### TypeScript

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

const client = new ExtendClient();

// For most production use cases, we recommend webhooks instead of polling
const result = await client.workflowRuns.createAndPoll({
  workflow: { id: "workflow_abc123", version: "latest" },
  package: {
    files: [
      { url: "https://example.com/msa.pdf" },
      { url: "https://example.com/amendment-1.pdf" },
      { id: "file_xK9mLPqRtN3vS8wF5hB2cQ" },
    ],
  },
});

console.log("Workflow run:", result.status);
```

#### Java

```java
import ai.extend.ExtendClient;
import ai.extend.resources.workflowruns.requests.WorkflowRunsCreateRequest;
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 java.util.List;

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

// For most production use cases, we recommend webhooks instead of polling
WorkflowRun result = client.workflowRuns().createAndPoll(WorkflowRunsCreateRequest.builder()
    .workflow(WorkflowReference.builder()
        .id("workflow_abc123")
        .version("latest")
        .build())
    .package_(WorkflowRunPackage.builder()
        .files(List.of(
            WorkflowRunPackageFilesItem.of(FileFromUrl.builder()
                .url("https://example.com/msa.pdf")
                .build()),
            WorkflowRunPackageFilesItem.of(FileFromUrl.builder()
                .url("https://example.com/amendment-1.pdf")
                .build()),
            WorkflowRunPackageFilesItem.of(FileFromId.builder()
                .id("file_xK9mLPqRtN3vS8wF5hB2cQ")
                .build())))
        .build())
    .build());

System.out.println("Workflow run: " + result.getStatus());
```

#### Go

```go
package main

import (
	"context"
	"fmt"
	"log"
	"time"

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

func main() {
	c := client.NewClient()
	ctx := context.TODO()

	run, err := c.WorkflowRuns.Create(ctx, &extend.WorkflowRunsCreateRequest{
		Workflow: &extend.WorkflowReference{
			ID:      "workflow_abc123",
			Version: extend.String("latest"),
		},
		Package: &extend.WorkflowRunPackage{
			Files: []*extend.WorkflowRunPackageFilesItem{
				{FileFromURL: &extend.FileFromURL{URL: "https://example.com/msa.pdf"}},
				{FileFromURL: &extend.FileFromURL{URL: "https://example.com/amendment-1.pdf"}},
				{FileFromID: &extend.FileFromID{ID: "file_xK9mLPqRtN3vS8wF5hB2cQ"}},
			},
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	// The Go SDK does not yet include a CreateAndPoll helper for workflow runs.
	// For most production use cases, we recommend webhooks instead of polling.
	for run.Status == extend.WorkflowRunStatusPending ||
		run.Status == extend.WorkflowRunStatusProcessing {
		time.Sleep(2 * time.Second)
		run, err = c.WorkflowRuns.Retrieve(ctx, run.ID, &extend.WorkflowRunsRetrieveRequest{})
		if err != nil {
			log.Fatal(err)
		}
	}

	fmt.Println("Workflow run:", run.Status)
}
```

#### 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", "version": "latest" },
    "package": {
      "files": [
        { "url": "https://example.com/msa.pdf" },
        { "url": "https://example.com/amendment-1.pdf" },
        { "id": "file_xK9mLPqRtN3vS8wF5hB2cQ" }
      ]
    }
  }'
```

For the full list of package constraints (file count, accepted input shapes, duplicates, and `outputs`), see [Package runs](/workflows/overview#package-runs).

## Response

A package run returns a single `workflow_run` object. Workflow runs always expose a `files` array, so the response shape does not change between single-file and package runs. For a package run, that array holds every file you submitted, in submission order.

Inside the run:

* The Parse step has one step run per file.
* The Extract step has a single step run whose extraction run covers the whole corpus. Its `file` is `null` and its `files` array lists every input file in order.
* `output.value` is one object for the entire package.
* Citations carry a `fileId` so you can trace each extracted value back to the source document. See [Citations and file provenance](/extraction/multifile#citations-and-file-provenance) for the field shape.

In the dashboard, the workflow run review screen shows a tab per step. The extraction tab shows the single combined output with per-file citations.

## Comparison

|                                 | Package workflow run                                        | Single-file workflow run | Batch workflow runs                                           |
| ------------------------------- | ----------------------------------------------------------- | ------------------------ | ------------------------------------------------------------- |
| **What it is**                  | One run over 2 to 50 files read together as a single corpus | One run over one file    | Many independent runs, one per file, created in a single call |
| **Workflow runs created**       | 1                                                           | 1                        | 1 per file                                                    |
| **Extraction output**           | One object across all files                                 | One object               | One object per run                                            |
| **Shared context across files** | Yes                                                         | N/A                      | No                                                            |
| **Requires a Collect step**     | Yes                                                         | No                       | No                                                            |

Use a package run when the answer spans documents. Use [batch runs](/general/batch-processing) when the documents are independent and you want a result per file.

## Next steps

#### [Multifile Extraction](/extraction/multifile)

The `package` parameter, file inputs, and citation provenance.

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

Every step type and routing rule, including Collect.

#### [Batch Processing](/general/batch-processing)

Run many files as independent workflow runs.

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

Full request and response schema.