# Dynamic Document API: PDF tools

> Merge, split, rotate, protect, unlock, inspect PDFs and convert them to PDF/A.

> Part of the Dynamic Document API documentation. Index of all pages for agents: [llms.txt](https://dynamicdocumentapi.com/llms.txt), every page in one file: [llms-full.txt](https://dynamicdocumentapi.com/llms-full.txt).

7 endpoints of the [API reference](https://dynamicdocumentapi.com/docs/api-reference/index.md), version 1.0.0. Interactive reference: https://dynamicdocumentapi.com/docs/api-reference#tag/pdf-tools. Machine-readable OpenAPI document: https://dynamicdocumentapi.com/openapi/openapi-public.json

## Servers

- https://api-eu.dynamicdocumentapi.com — EU (Germany)

## Endpoints

- `POST /v1/pdf-tools/merge` — Merge PDFs
- `POST /v1/pdf-tools/metadata` — Read or set PDF metadata
- `POST /v1/pdf-tools/pdfa` — Convert a PDF to PDF/A
- `POST /v1/pdf-tools/protect` — Password-protect a PDF
- `POST /v1/pdf-tools/rotate` — Rotate PDF pages
- `POST /v1/pdf-tools/split` — Split a PDF
- `POST /v1/pdf-tools/unlock` — Remove a PDF password

## POST /v1/pdf-tools/merge

**Merge PDFs**

Merge 2-200 PDFs into one document. Answers a Render object with `input_type: "pdf_tool"`; `delivery`, `mode`, `webhook`, `reference`, `metadata` and `test` behave as on `POST /v1/renders`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `Idempotency-Key` | header | string | Replays the stored response for 24 h; a different body with the same key → 422. |

Request body (`application/json`): MergeRequest

| Field | Type | Description |
|---|---|---|
| `bookmarks` | "filenames" \| "none" |  |
| `delivery` | DeliverySpec or null |  |
| `filename` | string or null |  |
| `metadata` | object or null |  |
| `mode` | "sync" \| "async" |  |
| `priority` | "normal" \| "low" |  |
| `reference` | string or null |  |
| `sources` (required) | array of PdfSourceSpec |  |
| `test` | boolean or null |  |
| `timeout_behavior` | "continue" \| "cancel" |  |
| `webhook` | WebhookSpec or null |  |

Responses:

- `200` Sync render finished (`status=succeeded`). With `delivery.type=binary` the body is the file.
- `202` Async render accepted, or the sync timeout was exceeded (`Location: /v1/renders/{id}`).
- `400` Malformed or invalid request (`errors[]` with JSON pointers), e.g. an unknown, switched-off or another template's rule at `/delivery/email/<i>/rule_id`.
- `401` Missing, invalid, expired or revoked credentials.
- `402` Render limit reached (`render_limit_reached`) or feature not on plan (`plan_feature_unavailable`, e.g. a `delivery.email` list below the Growth plan).
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified; or `region_not_enabled`: the workspace's enabled regions leave out this region.
- `404` Template or version not found.
- `408` Sync timeout exceeded and the render was canceled (`timeout_behavior=cancel`).
- `409` A request with this Idempotency-Key is still in progress.
- `410` Template deleted.
- `413` Payload or output too large.
- `422` Render failed (`render_id`, `render_error`; `render_failed` when the worker reports no more specific code), schema mismatch, URL not allowed or idempotency key reused. `einvoice_invalid`, `einvoice_validation_failed`, `pdfa_conversion_failed` and `pdfua_validation_failed` also carry the conformance report as `result`. `email_needs_hosted_file`: `delivery.email` lists rules, but a zero-retention render keeps no file to email. `render_canceled`: the sync render was canceled, or its job expired, before it finished. `scene_invalid`, `data_invalid`: a canvas template's scene can't be rendered (e.g. an output over 16,384 px per side), or its `data` isn't an object or lacks a required dynamic property.
- `429` Rate limited (`Retry-After`).
- `503` Region degraded (`Retry-After`).

## POST /v1/pdf-tools/metadata

**Read or set PDF metadata**

Without `set`: no file, and the render's `result` is `{pages, page_sizes, encrypted, pdf_version, metadata}`. `page_sizes` lists runs of pages shown at one size (`{"width": "210mm", "height": "297mm", "pages": "1-10"}`); `metadata` holds `title`, `author`, `subject`, `keywords` (a list), `creator`, `producer`, `created`, `modified` and `lang`, null when absent. A PDF that needs a password still answers `pages` and `encrypted: true`, with `metadata` null. With `set` (`title`, `author`, `subject`, `keywords`, `creator`, `producer`, `lang`): the PDF with only those fields changed (`""`, `null` or `[]` clears one) and the same `result` for it; an encrypted source fails with `pdf_password_required` (422). A read cannot use `delivery.type: "binary"`. 1 billed render. Answers a Render object with `input_type: "pdf_tool"`; `delivery`, `mode`, `webhook`, `reference`, `metadata` and `test` behave as on `POST /v1/renders`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `Idempotency-Key` | header | string | Replays the stored response for 24 h; a different body with the same key → 422. |

Request body (`application/json`): PdfMetadataRequest

| Field | Type | Description |
|---|---|---|
| `delivery` | DeliverySpec or null |  |
| `filename` | string or null |  |
| `metadata` | object or null |  |
| `mode` | "sync" \| "async" |  |
| `priority` | "normal" \| "low" |  |
| `reference` | string or null |  |
| `set` | object or null |  |
| `source` (required) | PdfSourceSpec | Exactly one of `url`, `render_id` (+ optional `file_id`), `upload_id` or `data_uri`. |
| `test` | boolean or null |  |
| `timeout_behavior` | "continue" \| "cancel" |  |
| `webhook` | WebhookSpec or null |  |

Responses:

- `200` Sync render finished (`status=succeeded`). With `delivery.type=binary` the body is the file.
- `202` Async render accepted, or the sync timeout was exceeded (`Location: /v1/renders/{id}`).
- `400` Malformed or invalid request (`errors[]` with JSON pointers), e.g. an unknown, switched-off or another template's rule at `/delivery/email/<i>/rule_id`.
- `401` Missing, invalid, expired or revoked credentials.
- `402` Render limit reached (`render_limit_reached`) or feature not on plan (`plan_feature_unavailable`, e.g. a `delivery.email` list below the Growth plan).
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified; or `region_not_enabled`: the workspace's enabled regions leave out this region.
- `404` Template or version not found.
- `408` Sync timeout exceeded and the render was canceled (`timeout_behavior=cancel`).
- `409` A request with this Idempotency-Key is still in progress.
- `410` Template deleted.
- `413` Payload or output too large.
- `422` Render failed (`render_id`, `render_error`; `render_failed` when the worker reports no more specific code), schema mismatch, URL not allowed or idempotency key reused. `einvoice_invalid`, `einvoice_validation_failed`, `pdfa_conversion_failed` and `pdfua_validation_failed` also carry the conformance report as `result`. `email_needs_hosted_file`: `delivery.email` lists rules, but a zero-retention render keeps no file to email. `render_canceled`: the sync render was canceled, or its job expired, before it finished. `scene_invalid`, `data_invalid`: a canvas template's scene can't be rendered (e.g. an output over 16,384 px per side), or its `data` isn't an object or lacks a required dynamic property.
- `429` Rate limited (`Retry-After`).
- `503` Region degraded (`Retry-After`).

## POST /v1/pdf-tools/pdfa

**Convert a PDF to PDF/A**

Converts any PDF to PDF/A-2b or PDF/A-3b (best effort: fonts must be embedded and the source must not be encrypted) and validates the result with veraPDF; the report is `result.conformance.pdfa`. A PDF that cannot be made conformant fails with `pdfa_conversion_failed`. 1 billed render. Answers a Render object with `input_type: "pdf_tool"`; `delivery`, `mode`, `webhook`, `reference`, `metadata` and `test` behave as on `POST /v1/renders`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `Idempotency-Key` | header | string | Replays the stored response for 24 h; a different body with the same key → 422. |

Request body (`application/json`): PdfaRequest

| Field | Type | Description |
|---|---|---|
| `delivery` | DeliverySpec or null |  |
| `filename` | string or null |  |
| `level` (required) | "2b" \| "3b" | PDF/A conformance level: `2b` (ISO 19005-2) or `3b` (ISO 19005-3, allows attachments). |
| `metadata` | object or null |  |
| `mode` | "sync" \| "async" |  |
| `priority` | "normal" \| "low" |  |
| `reference` | string or null |  |
| `source` (required) | PdfSourceSpec | Exactly one of `url`, `render_id` (+ optional `file_id`), `upload_id` or `data_uri`. |
| `test` | boolean or null |  |
| `timeout_behavior` | "continue" \| "cancel" |  |
| `webhook` | WebhookSpec or null |  |

Responses:

- `200` Sync render finished (`status=succeeded`). With `delivery.type=binary` the body is the file.
- `202` Async render accepted, or the sync timeout was exceeded (`Location: /v1/renders/{id}`).
- `400` Malformed or invalid request (`errors[]` with JSON pointers), e.g. an unknown, switched-off or another template's rule at `/delivery/email/<i>/rule_id`.
- `401` Missing, invalid, expired or revoked credentials.
- `402` Render limit reached (`render_limit_reached`) or feature not on plan (`plan_feature_unavailable`, e.g. a `delivery.email` list below the Growth plan).
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified; or `region_not_enabled`: the workspace's enabled regions leave out this region.
- `404` Template or version not found.
- `408` Sync timeout exceeded and the render was canceled (`timeout_behavior=cancel`).
- `409` A request with this Idempotency-Key is still in progress.
- `410` Template deleted.
- `413` Payload or output too large.
- `422` Render failed (`render_id`, `render_error`; `render_failed` when the worker reports no more specific code), schema mismatch, URL not allowed or idempotency key reused. `einvoice_invalid`, `einvoice_validation_failed`, `pdfa_conversion_failed` and `pdfua_validation_failed` also carry the conformance report as `result`. `email_needs_hosted_file`: `delivery.email` lists rules, but a zero-retention render keeps no file to email. `render_canceled`: the sync render was canceled, or its job expired, before it finished. `scene_invalid`, `data_invalid`: a canvas template's scene can't be rendered (e.g. an output over 16,384 px per side), or its `data` isn't an object or lacks a required dynamic property.
- `429` Rate limited (`Retry-After`).
- `503` Region degraded (`Retry-After`).

## POST /v1/pdf-tools/protect

**Password-protect a PDF**

AES-256 encryption with a user and/or owner password, as the `pdf.protect` render option does. Unset `permissions` flags take the same defaults: `print`, `print_high_res`, `copy` and `fill_forms` allowed, `modify`, `annotate` and `assemble` denied. An encrypted source fails with `pdf_password_required` (422). 1 billed render. Answers a Render object with `input_type: "pdf_tool"`; `delivery`, `mode`, `webhook`, `reference`, `metadata` and `test` behave as on `POST /v1/renders`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `Idempotency-Key` | header | string | Replays the stored response for 24 h; a different body with the same key → 422. |

Request body (`application/json`): ProtectRequest

| Field | Type | Description |
|---|---|---|
| `delivery` | DeliverySpec or null |  |
| `filename` | string or null |  |
| `metadata` | object or null |  |
| `mode` | "sync" \| "async" |  |
| `owner_password` | string or null |  |
| `permissions` | object or null |  |
| `priority` | "normal" \| "low" |  |
| `reference` | string or null |  |
| `source` (required) | PdfSourceSpec | Exactly one of `url`, `render_id` (+ optional `file_id`), `upload_id` or `data_uri`. |
| `test` | boolean or null |  |
| `timeout_behavior` | "continue" \| "cancel" |  |
| `user_password` | string or null |  |
| `webhook` | WebhookSpec or null |  |

Responses:

- `200` Sync render finished (`status=succeeded`). With `delivery.type=binary` the body is the file.
- `202` Async render accepted, or the sync timeout was exceeded (`Location: /v1/renders/{id}`).
- `400` Malformed or invalid request (`errors[]` with JSON pointers), e.g. an unknown, switched-off or another template's rule at `/delivery/email/<i>/rule_id`.
- `401` Missing, invalid, expired or revoked credentials.
- `402` Render limit reached (`render_limit_reached`) or feature not on plan (`plan_feature_unavailable`, e.g. a `delivery.email` list below the Growth plan).
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified; or `region_not_enabled`: the workspace's enabled regions leave out this region.
- `404` Template or version not found.
- `408` Sync timeout exceeded and the render was canceled (`timeout_behavior=cancel`).
- `409` A request with this Idempotency-Key is still in progress.
- `410` Template deleted.
- `413` Payload or output too large.
- `422` Render failed (`render_id`, `render_error`; `render_failed` when the worker reports no more specific code), schema mismatch, URL not allowed or idempotency key reused. `einvoice_invalid`, `einvoice_validation_failed`, `pdfa_conversion_failed` and `pdfua_validation_failed` also carry the conformance report as `result`. `email_needs_hosted_file`: `delivery.email` lists rules, but a zero-retention render keeps no file to email. `render_canceled`: the sync render was canceled, or its job expired, before it finished. `scene_invalid`, `data_invalid`: a canvas template's scene can't be rendered (e.g. an output over 16,384 px per side), or its `data` isn't an object or lacks a required dynamic property.
- `429` Rate limited (`Retry-After`).
- `503` Region degraded (`Retry-After`).

## POST /v1/pdf-tools/rotate

**Rotate PDF pages**

Adds `degrees` (90, 180 or 270; negative turns anticlockwise) to the rotation of the pages in `pages` (`"all"`, `"1,3-5"`, `"4-"`); other pages keep theirs. A page outside the document fails with `validation_error` (422), naming it; an encrypted source with `pdf_password_required` (unlock it first). 1 billed render. Answers a Render object with `input_type: "pdf_tool"`; `delivery`, `mode`, `webhook`, `reference`, `metadata` and `test` behave as on `POST /v1/renders`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `Idempotency-Key` | header | string | Replays the stored response for 24 h; a different body with the same key → 422. |

Request body (`application/json`): RotateRequest

| Field | Type | Description |
|---|---|---|
| `degrees` (required) | 90 \| 180 \| 270 \| -90 \| -180 \| -270 |  |
| `delivery` | DeliverySpec or null |  |
| `filename` | string or null |  |
| `metadata` | object or null |  |
| `mode` | "sync" \| "async" |  |
| `pages` | string |  |
| `priority` | "normal" \| "low" |  |
| `reference` | string or null |  |
| `source` (required) | PdfSourceSpec | Exactly one of `url`, `render_id` (+ optional `file_id`), `upload_id` or `data_uri`. |
| `test` | boolean or null |  |
| `timeout_behavior` | "continue" \| "cancel" |  |
| `webhook` | WebhookSpec or null |  |

Responses:

- `200` Sync render finished (`status=succeeded`). With `delivery.type=binary` the body is the file.
- `202` Async render accepted, or the sync timeout was exceeded (`Location: /v1/renders/{id}`).
- `400` Malformed or invalid request (`errors[]` with JSON pointers), e.g. an unknown, switched-off or another template's rule at `/delivery/email/<i>/rule_id`.
- `401` Missing, invalid, expired or revoked credentials.
- `402` Render limit reached (`render_limit_reached`) or feature not on plan (`plan_feature_unavailable`, e.g. a `delivery.email` list below the Growth plan).
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified; or `region_not_enabled`: the workspace's enabled regions leave out this region.
- `404` Template or version not found.
- `408` Sync timeout exceeded and the render was canceled (`timeout_behavior=cancel`).
- `409` A request with this Idempotency-Key is still in progress.
- `410` Template deleted.
- `413` Payload or output too large.
- `422` Render failed (`render_id`, `render_error`; `render_failed` when the worker reports no more specific code), schema mismatch, URL not allowed or idempotency key reused. `einvoice_invalid`, `einvoice_validation_failed`, `pdfa_conversion_failed` and `pdfua_validation_failed` also carry the conformance report as `result`. `email_needs_hosted_file`: `delivery.email` lists rules, but a zero-retention render keeps no file to email. `render_canceled`: the sync render was canceled, or its job expired, before it finished. `scene_invalid`, `data_invalid`: a canvas template's scene can't be rendered (e.g. an output over 16,384 px per side), or its `data` isn't an object or lacks a required dynamic property.
- `429` Rate limited (`Retry-After`).
- `503` Region degraded (`Retry-After`).

## POST /v1/pdf-tools/split

**Split a PDF**

One file per entry of `ranges` (`"1-3"`, `"4-"` to the last page, `"1,3-5"`) or per run of `every` pages, at most 200, named `<stem>-<n>.pdf` (n from 1; `<stem>` is `filename` without `.pdf`, else `split`) and listed in that order in `files`. With `zip: true` a single `<stem>.zip` holds them instead. `delivery.type: "binary"` needs a single output (`zip: true` or exactly one range); `base64` carries every file, and storage destinations get every file (`file.index` in path templates). A page outside the document fails with `validation_error` (422), naming it; an encrypted source with `pdf_password_required` (unlock it first). 1 billed render. Answers a Render object with `input_type: "pdf_tool"`; `delivery`, `mode`, `webhook`, `reference`, `metadata` and `test` behave as on `POST /v1/renders`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `Idempotency-Key` | header | string | Replays the stored response for 24 h; a different body with the same key → 422. |

Request body (`application/json`): SplitRequest

| Field | Type | Description |
|---|---|---|
| `delivery` | DeliverySpec or null |  |
| `every` | integer or null | One output file per run of this many pages instead. |
| `filename` | string or null |  |
| `metadata` | object or null |  |
| `mode` | "sync" \| "async" |  |
| `priority` | "normal" \| "low" |  |
| `ranges` | array of string or null | Page lists, one output file each: `"1-3"`, `"4-"` (to the last page), `"7"`, `"1,3-5"`. Pages are numbered from 1. |
| `reference` | string or null |  |
| `source` (required) | PdfSourceSpec | Exactly one of `url`, `render_id` (+ optional `file_id`), `upload_id` or `data_uri`. |
| `test` | boolean or null |  |
| `timeout_behavior` | "continue" \| "cancel" |  |
| `webhook` | WebhookSpec or null |  |
| `zip` | boolean | Deliver one ZIP (`<stem>.zip`) holding the parts instead of one file per part. |

Responses:

- `200` Sync render finished (`status=succeeded`). With `delivery.type=binary` the body is the file.
- `202` Async render accepted, or the sync timeout was exceeded (`Location: /v1/renders/{id}`).
- `400` Malformed or invalid request (`errors[]` with JSON pointers), e.g. an unknown, switched-off or another template's rule at `/delivery/email/<i>/rule_id`.
- `401` Missing, invalid, expired or revoked credentials.
- `402` Render limit reached (`render_limit_reached`) or feature not on plan (`plan_feature_unavailable`, e.g. a `delivery.email` list below the Growth plan).
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified; or `region_not_enabled`: the workspace's enabled regions leave out this region.
- `404` Template or version not found.
- `408` Sync timeout exceeded and the render was canceled (`timeout_behavior=cancel`).
- `409` A request with this Idempotency-Key is still in progress.
- `410` Template deleted.
- `413` Payload or output too large.
- `422` Render failed (`render_id`, `render_error`; `render_failed` when the worker reports no more specific code), schema mismatch, URL not allowed or idempotency key reused. `einvoice_invalid`, `einvoice_validation_failed`, `pdfa_conversion_failed` and `pdfua_validation_failed` also carry the conformance report as `result`. `email_needs_hosted_file`: `delivery.email` lists rules, but a zero-retention render keeps no file to email. `render_canceled`: the sync render was canceled, or its job expired, before it finished. `scene_invalid`, `data_invalid`: a canvas template's scene can't be rendered (e.g. an output over 16,384 px per side), or its `data` isn't an object or lacks a required dynamic property.
- `429` Rate limited (`Retry-After`).
- `503` Region degraded (`Retry-After`).

## POST /v1/pdf-tools/unlock

**Remove a PDF password**

Decrypts a PDF with its user or owner password and removes its restrictions. A wrong password fails with `pdf_password_required` (422); an unencrypted PDF comes back unchanged. 1 billed render. Answers a Render object with `input_type: "pdf_tool"`; `delivery`, `mode`, `webhook`, `reference`, `metadata` and `test` behave as on `POST /v1/renders`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `Idempotency-Key` | header | string | Replays the stored response for 24 h; a different body with the same key → 422. |

Request body (`application/json`): UnlockRequest

| Field | Type | Description |
|---|---|---|
| `delivery` | DeliverySpec or null |  |
| `filename` | string or null |  |
| `metadata` | object or null |  |
| `mode` | "sync" \| "async" |  |
| `password` (required) | string |  |
| `priority` | "normal" \| "low" |  |
| `reference` | string or null |  |
| `source` (required) | PdfSourceSpec | Exactly one of `url`, `render_id` (+ optional `file_id`), `upload_id` or `data_uri`. |
| `test` | boolean or null |  |
| `timeout_behavior` | "continue" \| "cancel" |  |
| `webhook` | WebhookSpec or null |  |

Responses:

- `200` Sync render finished (`status=succeeded`). With `delivery.type=binary` the body is the file.
- `202` Async render accepted, or the sync timeout was exceeded (`Location: /v1/renders/{id}`).
- `400` Malformed or invalid request (`errors[]` with JSON pointers), e.g. an unknown, switched-off or another template's rule at `/delivery/email/<i>/rule_id`.
- `401` Missing, invalid, expired or revoked credentials.
- `402` Render limit reached (`render_limit_reached`) or feature not on plan (`plan_feature_unavailable`, e.g. a `delivery.email` list below the Growth plan).
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified; or `region_not_enabled`: the workspace's enabled regions leave out this region.
- `404` Template or version not found.
- `408` Sync timeout exceeded and the render was canceled (`timeout_behavior=cancel`).
- `409` A request with this Idempotency-Key is still in progress.
- `410` Template deleted.
- `413` Payload or output too large.
- `422` Render failed (`render_id`, `render_error`; `render_failed` when the worker reports no more specific code), schema mismatch, URL not allowed or idempotency key reused. `einvoice_invalid`, `einvoice_validation_failed`, `pdfa_conversion_failed` and `pdfua_validation_failed` also carry the conformance report as `result`. `email_needs_hosted_file`: `delivery.email` lists rules, but a zero-retention render keeps no file to email. `render_canceled`: the sync render was canceled, or its job expired, before it finished. `scene_invalid`, `data_invalid`: a canvas template's scene can't be rendered (e.g. an output over 16,384 px per side), or its `data` isn't an object or lacks a required dynamic property.
- `429` Rate limited (`Retry-After`).
- `503` Region degraded (`Retry-After`).
