Documentation
One endpoint: send HTML — or a URL — and get a PDF back. Rendering happens in memory in the EU and nothing you send is written to disk.
Quickstart
- Create an API key in the dashboard. It's shown once — store it as a secret.
- POST your HTML to
https://api.pdfserve.eu/v1/renderwith the key in theAuthorizationheader. - Read the response body — it's the raw PDF.
curl -X POST https://api.pdfserve.eu/v1/render \
-H "Authorization: Bearer $PDFSERVE_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Invoice #1024</h1>",
"options": { "filename": "invoice.pdf" }
}' \
--output invoice.pdfAuthentication
Every request needs a bearer token — your API key, which begins with pdfs_live_:
Authorization: Bearer pdfs_live_xxxxxxxxxxxxKeys are managed from the dashboard and can be revoked at any time. We only store a hash — the raw key is shown once at creation.
Render a PDF
POST https://api.pdfserve.eu/v1/render
Request body
| Field | Type | Description |
|---|---|---|
| html | string | The HTML document to render. Max 5 MB. Provide exactly one of html or url. |
| url | string | Public http(s) URL we'll fetch and render. Must
resolve to a public address — see Rendering from a URL. |
| options.filename | string | Filename in the Content-Disposition header.
Default document.pdf. |
| options.base_url | string | Base URL for resolving relative asset links (images, stylesheets). |
| options.page_size | string | One of A3, A4, A5, Letter, Legal, Tabloid. Default A4. |
| options.orientation | string | portrait (default) or landscape. |
| options.header_html | string | HTML pulled into the top-centre @page margin box on
every page. Requires html input (not url). Increase margin so it has room to render. |
| options.footer_html | string | HTML pulled into the bottom-centre @page margin
box on every page. Same constraints as header_html. |
| options.watermark | string | Diagonal grey text stamped on every page (max 200 chars). Common values: "DRAFT", "CONFIDENTIAL". Applied after rendering, so it
sits on top of the document content. |
| options.password | string | Encrypts the PDF with AES-256. Readers must enter this password to open the file. 4–128 characters. |
| options.page_numbers | boolean | When true, stamps "Page N of M" in the bottom
margin (right if there's a footer, centre otherwise). Works with both html and url input. |
| options.margin | string | CSS margin shorthand — 1–4 lengths with a unit
(cm, mm, in, pt, px, pc),
e.g. "2cm" or "1cm 2cm 1cm 2cm". Default 2cm. Setting any page option overrides @page rules in your HTML; leave them unset to
keep your stylesheet's rules. |
Response
200 OK with Content-Type: application/pdf. The body is the raw PDF —
write it straight to a file or stream it to your user.
Loading external assets
Reference images and stylesheets with relative paths and set base_url so they resolve:
{
"html": "<img src=\"/logo.png\"> <link rel=\"stylesheet\" href=\"/print.css\">",
"options": { "base_url": "https://assets.example.eu/" }
}Rendering from a URL
Send url instead of html and we'll fetch the page and render it. The URL
(and any assets it pulls in) must resolve to a public address — requests to private, loopback,
link-local, or cloud-metadata addresses (e.g. 169.254.169.254) are refused with 422.
curl -X POST https://api.pdfserve.eu/v1/render \
-H "Authorization: Bearer $PDFSERVE_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/invoice/1024" }' \
--output invoice.pdfPage size, orientation, and margins
If you don't set these, any @page rules in your HTML are
used as-is. Setting any of them emits an @page stylesheet that overrides your HTML's page rules:
{
"html": "<h1>Quarterly report</h1>",
"options": {
"page_size": "A3",
"orientation": "landscape",
"margin": "1.5cm 2cm"
}
}Headers, footers, and page numbers
header_html and footer_html accept arbitrary HTML (with inline styles
for fonts and colours). They render into the top-centre and bottom-centre @page margin boxes on every page, so widen margin if your content is taller than the default
2 cm. page_numbers: true stamps "Page N of M"
automatically. Header and footer HTML are only supported with html input; page numbers work with both.
{
"html": "<h1>Invoice #1024</h1><p>...</p>",
"options": {
"margin": "2.5cm 2cm",
"header_html": "<div style=\"font:9pt sans-serif;color:#6b7280\">Acme Ltd \u2014 Invoice 1024</div>",
"footer_html": "<div style=\"font:9pt sans-serif;color:#6b7280\">Confidential</div>",
"page_numbers": true
}
}Watermark & password protection
watermark stamps diagonal grey text on every page (good
for "DRAFT" / "CONFIDENTIAL" overlays). password encrypts
the resulting PDF with AES-256 — the recipient is prompted before they can open it. Both
are applied after WeasyPrint renders the document.
{
"html": "<h1>Quarterly report</h1>",
"options": {
"watermark": "DRAFT",
"password": "share-with-finance"
}
}Errors
Errors return a JSON body with a detail field:
{ "detail": "Monthly render limit of 50 reached for the free plan" }| Status | Meaning |
|---|---|
| 401 | Missing or invalid API key. |
| 413 | HTML payload exceeds the 5 MB limit. |
| 422 | Invalid request body — missing or conflicting html/url,
a bad page option, or a refused URL (private / metadata address, unsupported scheme). |
| 429 | Monthly render limit reached for your plan. |