Endpoint

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.

Reference
View as Markdown

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.

Model

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.

Endpoint

Batch Endpoint

POST/v1/parse/batch

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

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

Headers

NameTypeRequiredLocationDescription
AuthorizationBearer <API_KEY>YesheaderUser-owned API key created in the DocuShell dashboard.
Idempotency-KeystringYesheaderRequired for every batch submit. Use a fresh key per logical batch and reuse it only for the exact same retry.

Request Fields

NameTypeRequiredLocationDescription
files[]file[]YesmultipartPDF uploads for the batch. Every file is preflighted before enqueue; one invalid file rejects the whole submit.
page_rangestringNomultipartShared page selector applied to every file, such as 1-3,5. Selected pages count toward the batch total page limit.
include_header_footerbooleanNomultipartShared setting. Set to true to keep repeated headers and footers in extracted output.Default: false
use_struct_treebooleanNomultipartShared setting. Set to true to prefer native tagged-PDF structure when available.Default: false
sanitizebooleanNomultipartShared setting. Set to true to mask email addresses, URLs, and phone numbers in extracted output.Default: false
reading_order`xycut` | `off`NomultipartShared reading-order strategy.
table_method`default` | `cluster`NomultipartShared table-detection strategy.
keep_line_breaksbooleanNomultipartShared setting. Set to true when text-oriented output should preserve original line breaks more closely.Default: false
output_mode`json` | `both` | `html` | `all`NomultipartBackward-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`NomultipartExplicit artifact list. Send repeated fields or a comma-separated value. Do not send this together with output_mode.
hybrid_mode`auto` | `full`NomultipartOptional shared hybrid triage override when the hybrid backend is enabled by operations.
image_output`off` | `embedded` | `external`NomultipartShared image handling for image-capable outputs.
x-docushell-webhook-urlstringNoheaderOptional public HTTPS endpoint for the terminal batch webhook. URLs with credentials, localhost, private, reserved, or metadata-service addresses are rejected.
x-docushell-webhook-secretstringNoheaderRequired 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-idstringNoheaderSaved 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.

Multipart batch submit

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"

Try It Now

Console placeholder for safe sandbox execution.

Coming soon

Accepted batch 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`.

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

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.

Batch not ready

425batch_not_ready

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

425 error

json

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

Batch file failed

409batch_file_failed

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

409 error

json

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

Expired output

410output_expired

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

410 error

json

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

Queue saturated

503server_busy

Returned when batch-specific queue backpressure rejects the submit.

503 error

json

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