> For the complete documentation index, see [llms.txt](https://docs.uptiq.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.uptiq.ai/document-ai/cookbooks/bulk-extraction.md).

# Extract many documents at once

**Goal.** Submit several documents in one call and collect their results together, instead of tracking a separate ID per file.

### Generated endpoint reference

Use [Bulk document extraction](/document-ai/developer-documentation/extraction-1/bulk-document-extraction-async.md) to submit files. Use [Get extraction details](/document-ai/developer-documentation/extraction-1/get-extraction-details.md) for individual results. Use [Get all extractions in a group](/document-ai/developer-documentation/extraction-1/get-all-extractions-in-a-multi-extraction-group-keyed-by-documenttype.md) for multi-extraction results.

### Before you start

* An API key, exported as `$DOCAI_API_KEY`.
* Two or more documents. They do not have to share a type.

### Step 1 — Build the batch

`POST /extract/bulk` takes a single required property, `files`. Each entry is a complete extraction request in its own right — it accepts every parameter the single-document endpoint does, so you can mix types, models and options within one batch.

```bash
python -c "
import base64, json

def entry(path, doc_type):
    return {
        'documentType': doc_type,
        'content': base64.b64encode(open(path,'rb').read()).decode()
    }

json.dump({'files': [
    entry('balance-sheet.pdf', 'BalanceSheet'),
    entry('profit-loss.pdf',   'ProfitAndLossStatement'),
    entry('bank-jan.pdf',      'BankStatements'),
]}, open('bulk.json','w'))
"
```

{% hint style="info" %}
Because each entry carries its own parameters, a batch is a convenience for submission rather than a shared processing context. Every file is extracted independently.
{% endhint %}

### Step 2 — Submit

```bash
curl -X POST "https://api-docai-uat.uptiq.ai/extract/bulk" \
  -H "X-Api-Key: $DOCAI_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @bulk.json
```

```json
{
  "status": "success",
  "message": "Documents queued for extraction",
  "total": 2,
  "successful": 2,
  "failed": 0,
  "results": [
    {
      "_id": "667f7754-0d2d-4b99-9e96-02e2f228edbe",
      "document_type": "BalanceSheet",
      "index": 0,
      "requestId": "947a7494-bc3c-4037-8693-296804a06eda",
      "status": "success"
    },
    {
      "_id": "7f2e3909-c9c1-4f88-aab8-112e51ab6524",
      "document_type": "ProfitAndLossStatement",
      "index": 1,
      "requestId": "679fa987-104e-4417-85fb-cb12e151a688",
      "status": "success"
    }
  ]
}
```

{% hint style="danger" %}
**A bulk submission is not a group.** It returns one independent `_id` per file and **no `extractionGroupId`** — there is nothing to retrieve as a set. Collect every `results[]._id` and poll each one with `GET /document-extractions/{id}`, exactly as in [Extract asynchronously and poll](/document-ai/cookbooks/async-extraction-and-polling.md).

The group endpoint exists for a different case entirely — see [Multi-extraction](#multi-extraction-several-documents-inside-one-file) below.
{% endhint %}

`index` is the position in the array you submitted, which is what lets you correlate a returned `_id` back to the file you sent. Use it — the response order is not otherwise guaranteed to be meaningful.

{% hint style="warning" %}
Note the field is `document_type` here, in snake\_case, while every other endpoint on this API uses `documentType`. A parser that assumes camelCase throughout will silently read `undefined`.
{% endhint %}

`status: "success"` on a result means the file was **queued**, not extracted. Watch `successful` against `total`: a file rejected at submission never gets an `_id`, so reconcile the counts before you start polling.

Note that base64 inflates payloads by roughly a third. A batch of large scans becomes a very large request body; prefer `file_url` per entry when your documents already have reachable URLs.

### Step 3 — Poll each result

```bash
jq -r '.results[]._id' bulk-response.json > ids.txt

while read -r ID; do
  STATUS=$(curl -s "https://api-docai-uat.uptiq.ai/document-extractions/$ID" \
    -H "X-Api-Key: $DOCAI_API_KEY" | jq -r '.extraction.status')
  echo "$ID $STATUS"
done < ids.txt
```

Remember the envelope trap: the job state is `extraction.status`, not the top-level `status`, which always reads `success`. See [Conventions](/document-ai/developer-documentation/getting-started/conventions.md).

### Step 4 — Handle partial success

Each document succeeds or fails on its own. One unreadable scan in a batch of twenty should not discard the other nineteen, so treat the batch as a set of independent outcomes rather than a single result — reconcile which `_id`s reached `Processed`, which reached `Failed`, and which files never got an `_id` at all.

### Multi-extraction: several documents inside one file

A different problem with a similar shape — and **this** is the one that produces a group. When a *single* PDF, Excel workbook or ZIP contains several logical documents, use `documentTypes` on a normal extraction call instead of `documentType`:

```json
{
  "content": "…",
  "documentTypes": [
    { "documentType": "BalanceSheet" },
    { "documentType": "ProfitAndLossStatement" }
  ]
}
```

```json
{
  "data": {
    "_id": "6e034efe-bd3e-4718-9304-46e4069b542d",
    "extractionGroupId": "464ca9a4-a4d6-437d-8664-d280560595fd",
    "childExtractionIds": [
      "9272c8ee-01c3-42fa-8e71-a9daf38e0092",
      "ae86fb62-b500-4467-a290-f483a8aa02d4"
    ],
    "extractions": [
      { "_id": "9272c8ee-…", "documentType": "BalanceSheet",             "status": "Pending" },
      { "_id": "ae86fb62-…", "documentType": "ProfitAndLossStatement", "status": "Pending" }
    ]
  }
}
```

{% hint style="info" %}
This call returns **`202`**, where a single-type `POST /extract` returns `200`. The status code varies with the request rather than the endpoint, so do not assert one specific success code for `/extract`.
{% endhint %}

Now you have an `extractionGroupId`, and the group endpoint applies:

```bash
curl -s "https://api-docai-uat.uptiq.ai/document-extractions/group/$GROUP_ID" \
  -H "X-Api-Key: $DOCAI_API_KEY"
```

```json
{
  "extractionGroupId": "464ca9a4-a4d6-437d-8664-d280560595fd",
  "count": 2,
  "status": "…",
  "parent": { },
  "extractions": { "BalanceSheet": { }, "ProfitAndLossStatement": { } }
}
```

| Field         | Meaning                                                             |
| ------------- | ------------------------------------------------------------------- |
| `count`       | Number of child extractions in the group                            |
| `extractions` | The children, **keyed by `documentType`** — an object, not an array |
| `parent`      | The parent record for the group                                     |

{% hint style="warning" %}
Because `extractions` is keyed by document type, two documents of the same type within one file collide. Duplicate keys are suffixed rather than collapsed, so do not assume exactly one key per type.
{% endhint %}

Completion webhooks for these carry `extractionGroupIndex` and `extractionGroupTotal` — see [Webhook payloads](/document-ai/developer-documentation/getting-started/webhook-payloads.md).

### Which one do you need

| You have                              | Use                                  | You get back                                                    |
| ------------------------------------- | ------------------------------------ | --------------------------------------------------------------- |
| Several files                         | `POST /extract/bulk` with `files`    | `results[]`, one `_id` per file. **No group** — poll each       |
| One file containing several documents | `POST /extract` with `documentTypes` | `extractionGroupId` + `childExtractionIds`. Retrieve as a group |

### When it goes wrong

| Symptom                                     | Cause                                            | Fix                                                 |
| ------------------------------------------- | ------------------------------------------------ | --------------------------------------------------- |
| `400` on submit                             | `files` missing, or not an array                 | It is the one required property                     |
| No `extractionGroupId` in the bulk response | There isn't one — bulk does not create a group   | Poll each `results[]._id` individually              |
| `404` from the group endpoint               | Using a bulk `_id` as a group ID                 | Group IDs come only from a `documentTypes` request  |
| Request body rejected as too large          | Base64 inflation across many files               | Use `file_url` per entry, or split the batch        |
| `successful` lower than `total`             | Some entries failed at submission                | Those files have no `_id`; reconcile before polling |
| Reading `undefined` for the document type   | Bulk returns `document_type`, not `documentType` | snake\_case, on this response only                  |
| Cannot tell which result is which file      | Relying on response order                        | Use `results[].index`, or set `metadata` per entry  |

### Related pages

* [Extraction API](/document-ai/developer-documentation/integration-guides/extraction.md) — `POST /extract/bulk` and `GET /document-extractions/group/{extraction_group_id}`.
* [Extract asynchronously and poll](/document-ai/cookbooks/async-extraction-and-polling.md) — the polling pattern, per child.
* [Build an event-driven pipeline](/document-ai/cookbooks/event-driven-pipeline.md) — a better fit for large batches.
* [Process a loan application packet](/document-ai/cookbooks/loan-application-intake.md) — a batch where the types are not known in advance.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.uptiq.ai/document-ai/cookbooks/bulk-extraction.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
