Template + JSON
Store the layout once and send only data. Best for documents you create thousands of times: invoices, certificates, statements. Learn more about PDF templates with variables and loops.
PDF Generation API
One REST API for every PDF your product sends: from a template and JSON, raw HTML, a URL or Markdown. Rendered in Chromium, delivered by link, file or webhook.
A PDF generation API is a web service your application calls to create PDF documents automatically. You send content, such as a template ID with JSON data, raw HTML, a URL or Markdown, and the service renders it and returns the finished PDF. It replaces the PDF library or headless browser you would otherwise build, host and maintain yourself.
| Inputs | Template + JSON, HTML + CSS, URL, Markdown |
|---|---|
| Outputs | PDF; PNG, JPEG or WebP from the same inputs |
| Rendering engine | Engine 2026.4 on Chromium (Chrome 153.0.8010.47), JavaScript on by default |
| Render time | Median 216 ms, p95 516 ms, p99 667 ms (queue + processing, excluding network)1 |
| Modes | Synchronous, asynchronous with webhooks, batches from JSON or CSV |
| Delivery | Signed URL, binary, base64, or your own S3-compatible bucket (Enterprise) |
| Reliability | Idempotency keys, signed webhooks with 8 delivery attempts, automatic retry of browser crashes |
| Price | Free: 50 renders/month. Paid from €15/month (billed annually) for 3,000 renders, auto top-ups from €6 per 1,000 |
1. Measured over 1,664 successful renders on engine 2026.4.
total_ms = time in the queue plus processing in the worker, excluding API
overhead and network transfer. p75 378 ms, p90 478 ms, maximum 2,261 ms.
Pick the input that matches where your content already lives. All four share the same engine, print options, delivery and webhooks, so you can mix them in one product.
Store the layout once and send only data. Best for documents you create thousands of times: invoices, certificates, statements. Learn more about PDF templates with variables and loops.
Send markup your app already renders and get it back as a PDF, with headers, page numbers and web fonts. See the HTML to PDF API.
Capture a live page after its JavaScript has finished, including pages behind a login. You can turn any webpage into a PDF.
Docs, reports and AI output, styled with a print theme and sanitized by default. Read about Markdown to PDF.
An event in your product, such as a payment, a finished course or a month-end job, triggers one API call.
Data is merged, the page loads in Chromium, scripts and fonts finish, and the PDF is printed.
In the response, as a signed link, in your storage bucket, or announced by webhook.
Every input is also available through one endpoint, POST /v1/renders,
where input.type selects template, HTML, URL or Markdown and
output.format selects PDF or an image format:
curl https://api.dynamicdocumentapi.com/v1/renders \
-H "Authorization: Bearer $DYNAMIC_DOCUMENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": { "type": "html", "html": "<h1>Welcome, {{ name }}</h1>" },
"data": { "name": "Acme Co." },
"output": { "format": "pdf", "pdf": { "paper": { "size": "Letter" } } },
"mode": "async",
"webhook": { "url": "https://example.com/hooks/pdf",
"events": ["render.succeeded", "render.failed"] }
}'
# 202 Accepted → { "id": "rnd_…", "status": "queued" }
const res = await fetch("https://api.dynamicdocumentapi.com/v1/renders", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DYNAMIC_DOCUMENT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
input: { type: "html", html: "<h1>Welcome, {{ name }}</h1>" },
data: { name: "Acme Co." },
output: { format: "pdf", pdf: { paper: { size: "Letter" } } },
mode: "async",
webhook: { url: "https://example.com/hooks/pdf", events: ["render.succeeded", "render.failed"] },
}),
});
const { id, status } = await res.json(); // 202, status "queued"
import os
import requests
res = requests.post(
"https://api.dynamicdocumentapi.com/v1/renders",
headers={"Authorization": f"Bearer {os.environ['DYNAMIC_DOCUMENT_API_KEY']}"},
json={
"input": {"type": "html", "html": "<h1>Welcome, {{ name }}</h1>"},
"data": {"name": "Acme Co."},
"output": {"format": "pdf", "pdf": {"paper": {"size": "Letter"}}},
"mode": "async",
"webhook": {"url": "https://example.com/hooks/pdf",
"events": ["render.succeeded", "render.failed"]},
},
timeout=30,
)
render = res.json() # 202, render["status"] == "queued"
<?php
$ch = curl_init('https://api.dynamicdocumentapi.com/v1/renders');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DYNAMIC_DOCUMENT_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'input' => ['type' => 'html', 'html' => '<h1>Welcome, {{ name }}</h1>'],
'data' => ['name' => 'Acme Co.'],
'output' => ['format' => 'pdf', 'pdf' => ['paper' => ['size' => 'Letter']]],
'mode' => 'async',
'webhook' => ['url' => 'https://example.com/hooks/pdf',
'events' => ['render.succeeded', 'render.failed']],
]),
]);
$render = json_decode(curl_exec($ch), true); // 202, status "queued"
"Dynamic" means the document changes with the data, not only the values in it. A template decides what appears, how often and in which format:
Generating one PDF is easy. Generating the right PDF every time, under load, with retries and without duplicates, is the part teams underestimate.
Synchronous by default. If a render outlasts your plan's sync limit, the API answers
202 and keeps going instead of failing. Use "mode": "async"
for long jobs and batches for thousands of documents from one template.
Webhooks follow the Standard Webhooks format: HMAC-SHA256 signatures in
webhook-signature, with a timestamp against replay. A delivery your server
does not accept is tried up to eight times over about 28 hours, and can be replayed by
hand.
Send an Idempotency-Key header, such as your invoice number. A retried
request within 24 hours returns the original render instead of a second document.
Test keys render for free with a "TEST" watermark and keep files for one day, so development and CI never touch your quota or your customers' documents.
Running headless Chrome yourself is a reasonable choice. It is also more work than the first prototype suggests. Here is what each part involves.
| Part | Self-built with headless Chrome | Dynamic Document API |
|---|---|---|
| First PDF | A day with Puppeteer or Playwright | Minutes with an API key |
| Production service | Queue, worker pool, timeouts, file storage, signed links, webhooks | Included |
| Browser updates | Chrome ships a major release every four weeks; you test and roll out each one | We test and pin engine releases |
| Fonts and rendering bugs | Missing fonts, fallback glyphs, cut table rows, late-loading charts | Font loading, wait conditions and page-break handling built in |
| Security | Blocking internal network access from user-supplied HTML is on you | Private and internal addresses blocked for every fetch |
| Templates | Templates in your code base, deployed with your app | Versioned templates with rollback, changeable without a deploy |
| Best when | Data must never leave your network, or volume is very high and steady | You want reliable PDFs without operating browsers |
Growth (€47/month, 15,000 renders), Pro (€119/month, 50,000) and Scale (€319/month, 200,000) are billed annually; Enterprise starts at €1,500. When a paid plan runs low, 1,000 renders are added automatically at €6 to €2 per 1,000, so a pipeline never stops at month end. See the full pricing, or talk to sales about higher volumes.
FAQ
HTML to PDF is one way to generate a PDF: you send finished markup. A PDF generation API covers the whole job: it also fills stored templates with JSON, captures URLs and converts Markdown, and adds what production needs, such as async jobs, webhooks, batches and file delivery.
The free plan includes 50 renders a month. Paid plans start at 3,000 renders a month for €15 billed annually and go to 200,000 on Scale; Enterprise starts at 1,000,000. When a paid plan runs low, 1,000 renders are added automatically at the plan's top-up price. On the free plan, render packs start at €50 for 10,000 renders.
Yes. A batch takes one template and a list of items, or a CSV file, and creates one PDF per item. Progress and completion arrive by webhook, failed items can be retried on their own, and all PDFs can be merged into one file. Batches are available on paid plans.
You get an error with a machine-readable code, such as a template error with line and
column, a navigation failure or a timeout, plus a render.failed webhook if
you subscribed. Browser crashes are retried automatically on a fresh browser. Errors in
your template or data are not retried, because the result would be the same.
Render payloads and generated files stay in the region of the API host you call. Files are delivered through signed, expiring links, and you decide how many days they are kept, up to your plan's maximum.
Yes. Every input that produces a PDF can also produce PNG, JPEG or WebP, for example a certificate as a printable PDF and as an image for social sharing, or a screenshot of a URL.
50 renders a month on the free plan, no credit card, auto top-ups on paid plans, no browser to run.
Start free