Authentication
All requests authenticate with a Bearer API key. Keys are scoped to a single organisation, never shown in plaintext after creation, and can be rotated or revoked without code changes — the next request with the old key returns 401.
Tokens start with `pdf_live_…` (production) or `pdf_test_…` (sandbox).
Authorization: Bearer pdf_live_X8s3...e2Qa
- ›Server-only — never embed an API key in a browser bundle or mobile binary.
- ›Last-used timestamp is updated on every request, visible in your dashboard.
- ›Revoke a key from `Settings → API keys`; effective on the next request.
Templates
Templates — the PDF the engine fills
A template is your source PDF plus the labels (named rectangles with coordinates) that mark where values are drawn. Labels are created manually via the visual editor or the `/labels/` bulk-upsert endpoint — they're not auto-detected.
GET /templates/
List all templates in your organisation.
Paginated, sorted by recency. Supports `?search=` for fuzzy name matching.
POST /templates/ multipart/form-data
Upload a new PDF as a template.
Fields
-
name string · required Display name. -
description string · optional Internal note for your team. -
file file · required PDF file, up to 25 MB.
Page previews run in the background. `previews_ready` flips to `true` when it's safe to render.
GET /templates/{id}/
Retrieve a template with its labels and page previews.
DELETE /templates/{id}/
Delete the template and its S3 artefacts.
Compilations
Compilations — one rendered PDF
Submit a values map for a template, get a `compilation` resource. Rendering is async: the response is immediate, the rendered PDF arrives in S3 and is exposed via a short-lived `download_url` once status transitions to `done`.
POST /compilations/ application/json
Create a single compilation.
Fields
-
template uuid · required Template to render. -
name string · optional Human-readable name. -
values object · required Map of `label_name → value`. Accepts strings, numbers, booleans and null — coerced server-side per field type.
Request body
{
"template": "3f0e4b2a-21c2-4d34-9c8d-9b0a5d6b3f11",
"values": {
"full_name": "Jordan Rivera",
"effective_date": "2026-06-01",
"agrees_to_terms": true
}
}
Response
HTTP/1.1 201 Created
{
"id": "c2a1...e9",
"status": "pending",
"download_url": null,
"created_at": "2026-05-27T10:13:51Z"
}
Status lifecycle: `pending → running → done | failed`. `download_url` is a pre-signed S3 URL valid for 5 minutes.
GET /compilations/{id}/
Poll a compilation, or fetch its `download_url` once `status == done`.
PATCH /compilations/{id}/
Re-render with new values, or rename the compilation.
Sending `values` triggers a re-render and consumes one quota unit. Sending only `name` is a free metadata edit.
DELETE /compilations/{id}/
Permanently delete the compilation and its rendered PDF from S3.
Batches
Batches — thousands of PDFs in one call
Two entry points: `/batches/json/` for programmatic use, `/batches/upload/` for CSV/XLSX. Both stream rows through a parallel worker pool and produce a single `result.zip` on completion.
POST /batches/json/ application/json
Submit up to 5 000 rows of values inline.
Fields
-
template uuid · required Template to render. -
rows array · required 1–5000 row objects. -
completion_webhook_endpoint uuid · optional Saved endpoint to notify on completion. -
completion_webhook_url url · optional Inline one-shot webhook URL.
Response
HTTP/1.1 202 Accepted
{
"id": "b1f2...77",
"source": "json",
"status": "pending",
"total": 0,
"expected_total": 2,
"succeeded": 0,
"failed": 0
}
Webhook precedence: saved endpoint → inline URL → org default endpoint. If none is configured, the batch runs without notification.
POST /batches/upload/ multipart/form-data
Submit a CSV or XLSX file (up to 20 MB).
Fields
-
template uuid · required Template to render. -
file file · required .csv or .xlsx, ≤ 20 MB. -
column_mapping object · optional JSON map of CSV column → label name.
GET /batches/{id}/
Poll batch progress. `succeeded + failed` reach `expected_total` when finished; `download_url` is non-null at that point.
Share requests
Share requests — let the recipient fill it themselves
Generate a token-protected URL pointing at a hosted compilation form. The recipient opens the link on any device, optionally verifies their email via OTP, fills the form, and submits.
POST /share-requests/
Create a single share request.
Fields
-
template uuid · required Template to expose. -
recipient_email email · optional If set, we deliver the link by email. -
verify_email bool · default false Gate submission behind an email-OTP challenge. -
send_copy_to_recipient bool · default false Auto-CC the final PDF on submit. -
notify_sender_on_complete bool · default false Email you when the recipient submits. -
prefill_values object · optional Values you already know. -
expires_in_days int 1–90 · default 7 Link lifetime.
POST /share-requests/batches/
Bulk-create share requests from a list of recipients.
Fields
-
template uuid · required Shared template. -
expires_in_days int · default 7 Applied to every link in the batch. -
requests array · required List of recipient rows.
Webhooks
Webhooks — events delivered to your endpoint
Configure an HTTPS endpoint that DataPDFEngine signs with HMAC-SHA256. Every payload includes the event name, the resource ID, and a stable timestamp so you can build idempotent handlers.
Signature verification (HMAC-SHA256)
Each outbound request carries three headers. Verify the signature before trusting the payload — and always compute the HMAC over the raw request body, before any JSON parsing.
-
X-DataPdfEngine-Event Event name, e.g. `batch.completed`. -
X-DataPdfEngine-Signature `sha256=<hex>` — HMAC of the raw body with your endpoint secret. -
X-DataPdfEngine-Timestamp ISO-8601 server time; reject requests older than ~5 minutes to defeat replays.
import crypto from "node:crypto";
export function verifyDataPdfEngineSignature(rawBody, headers, secret) {
const sig = headers["x-datapdfengine-signature"];
const ts = headers["x-datapdfengine-timestamp"];
if (!sig || !ts) return false;
if (Math.abs(Date.now() - Date.parse(ts)) > 5 * 60_000) return false;
const [scheme, hex] = sig.split("=");
if (scheme !== "sha256" || !hex) return false;
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(hex, "hex");
const b = Buffer.from(expected, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
POST /webhooks/endpoints/
Register a new endpoint. Response includes the `secret` once — store it.
POST /webhooks/endpoints/{id}/test/
Fire a `test.ping` synchronously and capture the response in the delivery log.
GET /webhooks/deliveries/
Inspect recent deliveries: status code, attempt count, response body.
Events
-
batch.completed Batch reached terminal state. Payload: `{ event, batch_id, status, total, succeeded, failed, download_url? }`. -
test.ping Sent synchronously from the dashboard when you click 'Send test'. Use it to validate signature handling and TLS reachability before going live.
Error model
Errors always come back as JSON with a stable `code` and a human-readable `detail`. HTTP status follows REST conventions.
| HTTP | Code | Detail |
| 400 | invalid | Payload didn't validate. `detail` lists the offending fields. |
| 401 | auth_failed | Missing, malformed, or revoked API key. |
| 402 | quota_exceeded | Plan quota for this feature is exhausted and overage is disabled. |
| 403 | feature_locked | Your plan doesn't include this feature. |
| 404 | not_found | Resource doesn't exist, or is owned by another org. |
| 422 | render_failed | The PDF engine couldn't render the values. |
| 429 | rate_limited | Hit the per-org API rate cap. Backoff hint in `Retry-After`. |
| 502 | delivery_failed | Outbound webhook target returned a non-2xx. Inspect the delivery log. |