What Is a PDF Generation API and How Do You Use One?
Updated
A PDF generation API takes HTML, a URL, or template data and returns a rendered PDF file, so you don't have to run a headless browser yourself. Use one when you need reliable, pixel-accurate documents (invoices, reports, booklets) at variable volume and would rather not operate a Chromium fleet. Doppio, for example, exposes a single render endpoint, supports direct S3 upload and async webhooks, and offers a free tier of 400 documents/month with no credit card required.
What the API actually does
You send a payload describing the page to render; the service runs it through a modern Chromium engine and hands back a PDF (or a PNG screenshot from the same API). The core inputs are:
- Source: raw HTML, a URL to load, or a saved template with injected data.
- Render options: things like
printBackground, and navigation options such aswaitUntil: ['networkidle0']so the render waits for resources to settle. - Delivery: the response itself, a webhook callback, or a push to your own S3-compatible bucket via presigned URLs.
Doppio's docs describe this as "one API call" from HTML or a URL to a production-ready file, with Flexbox, Grid, webfonts, charts, JavaScript, and Paged.js supported out of the box.
Managed API vs. self-hosted Puppeteer or wkhtmltopdf
The decision usually comes down to operations burden versus control.
| Dimension | Managed PDF API (e.g., Doppio) | Self-hosted Puppeteer / wkhtmltopdf |
|---|---|---|
| Infrastructure | Vendor runs managed Chromium | You patch, scale, and monitor browsers and workers |
| Scaling | Handled by the provider | You tune autoscaling and render workers |
| Rendering fidelity | Modern Chromium; CSS/JS/webfonts supported | Depends on your version; wkhtmltopdf lags on modern CSS |
| Delivery options | Response, webhook, direct S3 upload | You build each path |
| Data handling | Isolated renders; EU-hosted option | Fully under your control |
| Cost model | Per-usage (check current pricing) | Engineering time plus compute |
Choose the managed API when document generation is not your core product and traffic spikes or heavy pages would otherwise require babysitting render workers. Choose self-hosting when you need full control over the runtime or have constraints the vendor can't meet. Doppio states it is EU-hosted and privacy-first, which matters if document residency is a requirement.
The typical request flow
- Authenticate with a bearer token in the
Authorizationheader. - Build the payload with a
pageobject containingpdfoptions and agotoblock (URL plus navigation options), or a template reference. - Choose sync or async: a synchronous call returns the file; async runs complex renders in the background and calls your webhook when done.
- Deliver the result to your app, a webhook workflow, or S3-compatible storage.
A minimal synchronous call, based on Doppio's published example:
const response = await fetch('https://api.doppio.sh/v1/render/pdf/sync', {
method: 'POST',
headers: {
'Authorization': 'Bearer <YOUR_API_KEY>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
page: {
pdf: { printBackground: true },
goto: {
url: 'https://example.doppio.sh/information-booklet.html',
options: { waitUntil: ['networkidle0'] }
}
}
})
});
Expected result: a PDF response you can save or stream. Verify by opening the file and checking that backgrounds, fonts, and any charts rendered.
What to check before adopting
- CSS/JS and webfont support: confirm your layout (Grid, Flexbox, custom fonts, client-side charts) renders correctly; test a representative page, not a toy one.
- Isolation and hosting region: ask whether renders are isolated and where data is hosted, especially for sensitive documents.
- Delivery paths: verify webhook reliability and S3 presigned-URL upload if you need files in your own bucket.
- Pricing and limits: check the pricing page for current tiers; Doppio advertises 400 documents/month free and 99.995% uptime last year, but confirm terms before committing.
- Scale signals: Doppio cites 50M+ documents per year across 4000+ teams—useful context, not a guarantee for your workload.
Troubleshooting common render failures
- Blank or partial pages: resources hadn't loaded. Use
waitUntil: ['networkidle0']or wait for a specific selector. - Missing backgrounds or images: enable
printBackgroundand confirm assets are reachable from the render environment (not behind auth). - Fonts wrong: ensure webfonts load over the network or are embedded; test with the exact font files you ship.
- Timeouts on heavy pages: move to the async endpoint with a webhook instead of holding a synchronous connection.
Start with one real document, confirm fidelity and delivery, then scale volume—rather than building a rendering stack you'd have to maintain.