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

> Extract structured data from multiple documents in a single run using the package field.

This page covers standalone `/extract_runs` calls. To run the same extraction inside a workflow, with validation, review, and routing applied to the combined result, see [Multifile Extraction in Workflows](/workflows/multifile-extraction).

Multifile extraction lets you run a single extraction over a collection of files with a shared context. Useful when a field's value must be chosen from the best among a variety of sources, or when values are derived from multiple files.

**Example 1:** A contract contains a number of amendments and those amendments supersede the original content.

**Example 2:** A user submits multiple pictures of a long receipt that should be extracted together.

## How it works

Pass a `package` instead of a `file` on your request. The `package.files` array accepts up to 50 entries, each either a URL or an existing Extend file ID. The API ingests all files concurrently, runs extraction across the full corpus, and returns a single `ExtractRun` with a `files` array in the response (and `file: null`).

## Quick start

#### Python

```python
from extend_ai import Extend

client = Extend()

result = client.extract_runs.create_and_poll(
    extractor={"id": "ex_abc123"},
    package={
        "files": [
            {"url": "https://example.com/invoice1.pdf"},
            {"url": "https://example.com/invoice2.pdf"},
            {"url": "https://example.com/invoice3.pdf"},
        ]
    },
)

print(result.output.value)
print("Files processed:", [f.name for f in result.files])
```

#### TypeScript

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

const client = new ExtendClient();

const result = await client.extractRuns.createAndPoll({
  extractor: { id: "ex_abc123" },
  package: {
    files: [
      { url: "https://example.com/invoice1.pdf" },
      { url: "https://example.com/invoice2.pdf" },
      { url: "https://example.com/invoice3.pdf" },
    ],
  },
});

console.log(result.output?.value);
console.log("Files processed:", result.files?.map((f) => f.name));
```

#### Java

```java
import ai.extend.ExtendClient;
import ai.extend.resources.extractruns.requests.ExtractRunsCreateRequest;
import ai.extend.resources.extractruns.types.ExtractRunsCreateRequestExtractor;
import ai.extend.types.ExtractRun;
import ai.extend.types.FileFromUrl;
import ai.extend.types.MultiFileRunPackage;
import ai.extend.types.MultiFileRunPackageFilesItem;
import java.util.List;

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

ExtractRun result = client.extractRuns().createAndPoll(
    ExtractRunsCreateRequest.builder()
        .extractor(ExtractRunsCreateRequestExtractor.builder()
            .id("ex_abc123")
            .build())
        .package_(MultiFileRunPackage.builder()
            .files(List.of(
                MultiFileRunPackageFilesItem.of(FileFromUrl.builder().url("https://example.com/invoice1.pdf").build()),
                MultiFileRunPackageFilesItem.of(FileFromUrl.builder().url("https://example.com/invoice2.pdf").build()),
                MultiFileRunPackageFilesItem.of(FileFromUrl.builder().url("https://example.com/invoice3.pdf").build())
            ))
            .build())
        .build());

System.out.println(result.getOutput());
System.out.println("Files processed: " + result.getFiles());
```

#### Go

```go
package main

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

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

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

	run, err := c.ExtractRuns.Create(ctx, &extend.ExtractRunsCreateRequest{
		Extractor: &extend.ExtractRunsCreateRequestExtractor{
			ID: "ex_abc123",
		},
		Package: &extend.MultiFileRunPackage{
			Files: []*extend.MultiFileRunPackageFilesItem{
				{FileFromURL: &extend.FileFromURL{URL: "https://example.com/invoice1.pdf"}},
				{FileFromURL: &extend.FileFromURL{URL: "https://example.com/invoice2.pdf"}},
				{FileFromURL: &extend.FileFromURL{URL: "https://example.com/invoice3.pdf"}},
			},
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	// The Go SDK does not yet include a CreateAndPoll helper, so poll for completion.
	for run.Status == extend.ProcessorRunStatusPending || run.Status == extend.ProcessorRunStatusProcessing {
		time.Sleep(2 * time.Second)
		run, err = c.ExtractRuns.Retrieve(ctx, run.ID, &extend.ExtractRunsRetrieveRequest{})
		if err != nil {
			log.Fatal(err)
		}
	}

	// Output is only present when the run reaches PROCESSED.
	if run.Status != extend.ProcessorRunStatusProcessed {
		log.Fatalf("extract run ended with status %s", run.Status)
	}

	fmt.Println(run.Output.GetExtractOutputJSON().Value)
	for _, f := range run.Files {
		fmt.Println("Processed:", f.Name)
	}
}
```

#### cURL

```bash
curl -X POST https://api.extend.ai/extract_runs \
  -H "x-extend-api-version: 2026-02-09" \
  -H "Authorization: Bearer $EXTEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "extractor": { "id": "ex_abc123" },
    "package": {
      "files": [
        { "url": "https://example.com/invoice1.pdf" },
        { "url": "https://example.com/invoice2.pdf" },
        { "url": "https://example.com/invoice3.pdf" }
      ]
    }
  }'
```

## File inputs

Each entry in `package.files` can be either:

| Input   | Shape                      | Notes                                     |
| ------- | -------------------------- | ----------------------------------------- |
| URL     | `{ "url": "https://..." }` | Presigned URLs recommended for production |
| File ID | `{ "id": "file_..." }`     | Reuse a previously uploaded Extend file   |

Raw text (`text`) and base64 inputs are not supported in multifile packages — use `url` or `id`.

You can mix URLs and file IDs in the same package:

```json
{
  "package": {
    "files": [
      { "url": "https://example.com/cover-page.pdf" },
      { "id": "file_xK9mLPqRtN3vS8wF5hB2cQ" },
      { "url": "https://example.com/appendix.pdf" }
    ]
  }
}
```

## Response

A multifile run returns the same `ExtractRun` shape as a single-file run, with two differences:

* **`file`** is `null`
* **`files`** is an ordered array of `FileSummary` objects, one per input file in submission order

```json
{
  "object": "extract_run",
  "id": "exr_3f1j6I1gsw5k96xFiCnkM",
  "status": "PROCESSED",
  "file": null,
  "files": [
    { "object": "file", "id": "file_aaa", "name": "invoice1.pdf", ... },
    { "object": "file", "id": "file_bbb", "name": "invoice2.pdf", ... },
    { "object": "file", "id": "file_ccc", "name": "invoice3.pdf", ... }
  ],
  "output": {
    "value": { ... },
    "metadata": { ... }
  }
}
```

`output.value` is a single object covering the whole corpus — not one object per file. Design your extractor schema to describe what you want extracted across all files together.

## Citations and file provenance

Looking for citations on an ordinary single-file extraction (what they contain, how to enable them, bounding-box coordinates)? See [Citations](/extraction/response-format#citations) on the Response Format page. This section covers only what changes when the run spans a package of files.

Every citation carries a `fileId` telling you which input file the cited content came from. Join it against the run's `files` array to attribute each extracted value to its source document — a citation's `page.number` is always relative to that file, not to the corpus as a whole.

```json
{
  "files": [
    { "object": "file", "id": "file_aaa", "name": "invoice1.pdf" },
    { "object": "file", "id": "file_bbb", "name": "invoice2.pdf" }
  ],
  "output": {
    "value": { "total_amount": 15735.1 },
    "metadata": {
      "total_amount": {
        "logprobsConfidence": 0.98,
        "citations": [
          {
            "fileId": "file_bbb",
            "page": { "number": 2, "width": 612, "height": 792 },
            "referenceText": "TOTAL  $15,735.1"
          }
        ]
      }
    }
  }
}
```

`fileId` is present whether or not bounding-box citations are enabled, so per-field provenance is available on every multifile run. See [Citations](/extraction/response-format#citations) for the full citation shape.

## Multifile vs batch

Multifile extraction and [batch processing](/general/batch-processing) are complementary but different:

|                       | Multifile (`package`)                    | Batch (`/extract_runs/batch`)                     |
| --------------------- | ---------------------------------------- | ------------------------------------------------- |
| **What it is**        | One run across N files — a single corpus | N independent single-file runs submitted together |
| **Output**            | One `output.value` combining all files   | One `output.value` per file                       |
| **Use when**          | Fields span multiple documents           | Each file is extracted independently              |
| **Files per request** | 1–50                                     | Up to 1,000                                       |

Use multifile when your extractor schema is designed to aggregate across a set of documents. Use batch when you just want to submit many independent files efficiently.