# Parse Batch

`POST /v1/parse/batch` accepts up to 10 PDFs in one request, applies the same parse options to all of them, and reports per-file status and artifacts under a single `batch_id`.

Source: https://docs.docushell.com/parse-batch
Category: Reference

## Related

- [Parse PDF](/parse-pdf.md): Single-file parsing, artifact formats, and extraction controls.
- [Troubleshooting](/troubleshooting.md): Batch preflight rejections and download-state errors.

## Summary

| Label | Value | Description |
| --- | --- | --- |
| Limits | 10 files / 100 MB / 500 pages | Default v1 caps, plus 2 batch submits per minute. |
| Idempotency | Required | One-hour window from submit, shorter than the Stripe-style 24-hour convention. |
| Artifact TTL | 1 hour | Measured from terminal completion. Expired downloads return `410 output_expired`. |

## How Batches Behave

- Preflight is all-or-nothing: one invalid file rejects the whole submit before anything is queued.
- Parse options are shared across the batch. v1 has no per-file settings.
- Polling is the source of truth. Webhooks are best-effort terminal notifications with bounded retry.
- Terminal statuses are `completed`, `completed_with_failures`, and `failed`. No per-file retry in v1.
- `estimated_credits` is an estimate only: `max(10, selected_pages)` per file. v1 does not settle credits on this lane.
- Every status and download request is owner-scoped. Unknown batches or owner mismatches return `404`.

## Batch Endpoint

- Method: `POST`
- Path: `/v1/parse/batch`
- Auth: Bearer token required on submit, status, and artifact download requests.
- Idempotency: Required `Idempotency-Key`. The default idempotency window is one hour from submit, intentionally shorter than Stripe-style 24-hour idempotency. Reusing the same key and request replays the accepted response; reusing the key with a different request returns `409 idempotency_key_reused`.
- Content type: `multipart/form-data`

Submit up to 10 PDFs as one async parse batch with shared parse options and per-file artifacts.

### Headers

| Name | Type | Required | Location | Description |
| --- | --- | --- | --- | --- |
| Authorization | Bearer <API_KEY> | Yes | header | User-owned API key created in the DocuShell dashboard. |
| Idempotency-Key | string | Yes | header | Required for every batch submit. Use a fresh key per logical batch and reuse it only for the exact same retry. |

### Request Fields

| Name | Type | Required | Location | Description |
| --- | --- | --- | --- | --- |
| files[] | file[] | Yes | multipart | PDF uploads for the batch. Every file is preflighted before enqueue; one invalid file rejects the whole submit. |
| page_range | string | No | multipart | Shared page selector applied to every file, such as `1-3,5`. Selected pages count toward the batch total page limit. |
| include_header_footer | boolean | No | multipart | Shared setting. Set to `true` to keep repeated headers and footers in extracted output. Default: false |
| use_struct_tree | boolean | No | multipart | Shared setting. Set to `true` to prefer native tagged-PDF structure when available. Default: false |
| sanitize | boolean | No | multipart | Shared setting. Set to `true` to mask email addresses, URLs, and phone numbers in extracted output. Default: false |
| reading_order | `xycut` \| `off` | No | multipart | Shared reading-order strategy. |
| table_method | `default` \| `cluster` | No | multipart | Shared table-detection strategy. |
| keep_line_breaks | boolean | No | multipart | Shared setting. Set to `true` when text-oriented output should preserve original line breaks more closely. Default: false |
| output_mode | `json` \| `both` \| `html` \| `all` | No | multipart | Backward-compatible artifact bundle selector. Do not send this together with `formats`. Default: both |
| formats | `json` \| `markdown` \| `html` \| `text` \| `annotated_pdf` \| `markdown_with_html` \| `markdown_with_images` \| `tagged_pdf` | No | multipart | Explicit artifact list. Send repeated fields or a comma-separated value. Do not send this together with `output_mode`. |
| hybrid_mode | `auto` \| `full` | No | multipart | Optional shared hybrid triage override when the hybrid backend is enabled by operations. |
| image_output | `off` \| `embedded` \| `external` | No | multipart | Shared image handling for image-capable outputs. |
| x-docushell-webhook-url | string | No | header | Optional public HTTPS endpoint for the terminal batch webhook. URLs with credentials, localhost, private, reserved, or metadata-service addresses are rejected. |
| x-docushell-webhook-secret | string | No | header | Required when `x-docushell-webhook-url` is present. Must be 16-256 characters with sufficient variety; do not reuse an API key. |
| x-docushell-webhook-endpoint-id | string | No | header | Saved managed webhook endpoint id. Use this instead of sending a per-request webhook URL and secret. |

### Request Notes

- Batch parse is async-only. Submit returns `202`; poll `GET /v1/parse/batch/:batchId` for truth.
- All files are preflighted before enqueue. Empty, non-PDF, corrupt, password-protected, oversized, invalid-page-range, per-file page-limit, total-byte-limit, and total-page-limit failures reject the whole submit.
- Default v1 limits are 10 files, 100 MB total upload, 500 selected pages, and 2 batch submits per minute.
- Parse options are shared across the batch. v1 does not support per-file parse settings.
- Send either `output_mode` or `formats`, not both. Only one markdown-style format (`markdown`, `markdown_with_html`, or `markdown_with_images`) can be requested.
- The batch lane has separate backpressure. Queue saturation returns `503 server_busy` with `Retry-After`; rate limits return `429`.
- Batch responses report `estimated_credits` only: each file estimates `max(10, selected_pages)` credits. v1 does not perform final credit settlement on this lane.
- Every status and download request is owner-scoped. Unknown batches, files, or owner mismatches return `404`.
- Artifacts expire one hour after terminal completion by default. Batch idempotency expires one hour after submit by default.
- Terminal statuses are `completed`, `completed_with_failures`, and `failed`. No per-file retry is attempted in v1.
- Webhooks are signed best-effort terminal notifications with short bounded retry. Polling remains the source of truth.

### Sample Requests

```bash
curl -X POST "https://api.docushell.com/api/v1/parse/batch" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: parse-batch-demo-001" \
  -H "x-docushell-webhook-url: https://example.com/docushell/webhooks" \
  -H "x-docushell-webhook-secret: replace_with_a_long_random_secret" \
  -F "files[]=@./report-q1.pdf;type=application/pdf" \
  -F "files[]=@./report-q2.pdf;type=application/pdf" \
  -F "page_range=1-5" \
  -F "formats=json,markdown"
```

### Queued Response

```json
{
  "batch_id": "9c7f2f2e-4f4b-4cbf-bb12-7fd3c1f4f2ab",
  "service": "parse-pdf",
  "status": "queued",
  "counts": {
    "total": 2,
    "queued": 2,
    "processing": 0,
    "completed": 0,
    "failed": 0
  },
  "usage": {
    "total_upload_bytes": 1843200,
    "total_selected_pages": 12
  },
  "estimated_credits": 20,
  "created_at": "2026-05-23T10:12:30.000Z",
  "updated_at": "2026-05-23T10:12:30.000Z",
  "completed_at": null,
  "expires_at": null,
  "webhook_delivery": {
    "status": "pending"
  },
  "metrics": null,
  "files": [
    {
      "file_id": "file_6c27f6d4-43c1-48c6-a3c8-3a89d0a3cf20",
      "status": "queued",
      "page_count": 8,
      "billable_pages": 8,
      "estimated_credits": 10
    },
    {
      "file_id": "file_95ac2fb4-060d-427c-9f86-864747dfb935",
      "status": "queued",
      "page_count": 4,
      "billable_pages": 4,
      "estimated_credits": 10
    }
  ],
  "links": {
    "status": "/v1/parse/batch/9c7f2f2e-4f4b-4cbf-bb12-7fd3c1f4f2ab",
    "download": "/v1/parse/batch/9c7f2f2e-4f4b-4cbf-bb12-7fd3c1f4f2ab/download"
  }
}
```

The same `Idempotency-Key` with the same request replays this response. A different request with that key returns `409 idempotency_key_reused`.

### Status Response

```json
{
  "batch_id": "9c7f2f2e-4f4b-4cbf-bb12-7fd3c1f4f2ab",
  "service": "parse-pdf",
  "status": "completed_with_failures",
  "counts": {
    "total": 2,
    "queued": 0,
    "processing": 0,
    "completed": 1,
    "failed": 1
  },
  "usage": {
    "total_upload_bytes": 1843200,
    "total_selected_pages": 12
  },
  "estimated_credits": 20,
  "created_at": "2026-05-23T10:12:30.000Z",
  "updated_at": "2026-05-23T10:14:04.000Z",
  "completed_at": "2026-05-23T10:14:04.000Z",
  "expires_at": "2026-05-23T11:14:04.000Z",
  "webhook_delivery": {
    "status": "delivered"
  },
  "metrics": {
    "queue_wait_ms": 214,
    "duration_ms": 82341
  },
  "files": [
    {
      "file_id": "file_6c27f6d4-43c1-48c6-a3c8-3a89d0a3cf20",
      "status": "completed",
      "page_count": 8,
      "billable_pages": 8,
      "estimated_credits": 10,
      "artifacts": {
        "json_download": "/v1/parse/batch/9c7f2f2e-4f4b-4cbf-bb12-7fd3c1f4f2ab/files/file_6c27f6d4-43c1-48c6-a3c8-3a89d0a3cf20/download?format=json",
        "markdown_download": "/v1/parse/batch/9c7f2f2e-4f4b-4cbf-bb12-7fd3c1f4f2ab/files/file_6c27f6d4-43c1-48c6-a3c8-3a89d0a3cf20/download?format=markdown"
      }
    },
    {
      "file_id": "file_95ac2fb4-060d-427c-9f86-864747dfb935",
      "status": "failed",
      "page_count": 4,
      "billable_pages": 4,
      "estimated_credits": 10,
      "failure_code": "corrupt_pdf"
    }
  ],
  "links": {
    "status": "/v1/parse/batch/9c7f2f2e-4f4b-4cbf-bb12-7fd3c1f4f2ab",
    "download": "/v1/parse/batch/9c7f2f2e-4f4b-4cbf-bb12-7fd3c1f4f2ab/download"
  }
}
```

`completed_with_failures` means at least one file produced artifacts and at least one accepted file failed after enqueue. Check each file status before downloading.

### Download Samples

#### Batch ZIP download

```bash
curl "https://api.docushell.com/api/v1/parse/batch/9c7f2f2e-4f4b-4cbf-bb12-7fd3c1f4f2ab/download" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --output parse-batch.zip
```

The batch ZIP is generated on demand from completed file artifacts and is not a durable artifact itself.

#### Per-file artifact download

```bash
curl "https://api.docushell.com/api/v1/parse/batch/9c7f2f2e-4f4b-4cbf-bb12-7fd3c1f4f2ab/files/file_6c27f6d4-43c1-48c6-a3c8-3a89d0a3cf20/download?format=json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --output report.json
```

Use the file-specific download route when you want one artifact instead of the generated batch ZIP.

### Artifacts

- Per-file download links appear under `files[].artifacts` after each completed file is promoted.
- The batch ZIP is generated on demand from completed file artifacts, streamed, and removed after the response finishes.
- Per-file artifact downloads can be retried until the batch expires.
- The per-file `format` query must match an artifact requested for the batch.
- Original filenames are not part of the public status payload or webhook logs.

### Poll And Download

- Poll `GET /v1/parse/batch/:batchId` until `status` becomes `completed`, `completed_with_failures`, or `failed`.
- When the batch completes or partially completes, use `GET /v1/parse/batch/:batchId/download` for the generated ZIP or use each `files[].artifacts.*_download` link for a specific file artifact.
- If a file status is `failed`, its per-file download returns `409 batch_file_failed`. Continue downloading completed files until `expires_at`.

### Failure Notes

- `invalid_pdf`, `corrupt_pdf`, `password_protected`, `invalid_page_range`, and `page_limit_exceeded` can be returned during preflight before a batch is accepted.
- `server_busy` with `Retry-After` means the dedicated batch queue or active batch lane is saturated. Retry later with the same Idempotency-Key only for the exact same request.
- Download `400` means the requested format was not requested for this batch.
- Download `425 batch_not_ready` means the batch or file is not terminal yet.
- Download `409 batch_file_failed` means that accepted file reached a terminal failed state.
- Download `410 output_expired` means the artifact TTL has passed.

### Error Examples

#### Batch not ready — `425 batch_not_ready`

Returned when a batch or file download is attempted before terminal status.

```json
{
  "error": {
    "code": "batch_not_ready",
    "message": "Batch is not ready.",
    "type": "invalid_request_error",
    "request_id": "req_01JX8Y62XCDNZ2BM7TBM2M9Q8E"
  }
}
```

#### Batch file failed — `409 batch_file_failed`

Returned when a per-file artifact is requested for a file that failed after enqueue.

```json
{
  "error": {
    "code": "batch_file_failed",
    "message": "Batch file failed.",
    "type": "invalid_request_error",
    "request_id": "req_01JX8Y62XCDNZ2BM7TBM2M9Q8E"
  }
}
```

#### Expired output — `410 output_expired`

Returned after the one-hour default TTL for completed batch artifacts passes.

```json
{
  "error": {
    "code": "output_expired",
    "message": "Batch output expired.",
    "type": "invalid_request_error",
    "request_id": "req_01JX8Y62XCDNZ2BM7TBM2M9Q8E"
  }
}
```

#### Queue saturated — `503 server_busy`

Returned when batch-specific queue backpressure rejects the submit.

```json
{
  "error": {
    "code": "server_busy",
    "message": "The parse batch queue is busy. Retry later.",
    "type": "internal_error",
    "request_id": "req_01JX8Y62XCDNZ2BM7TBM2M9Q8E"
  }
}
```
