Most apps end up generating invoices, receipts, or certificates. You can run headless Chrome yourself, or you can send a template and some JSON and get a PDF back.
We built getpdf, a template-to-PDF REST API. You can try it in the browser with no account at getpdf.dev/#try. Pick invoice, receipt, or certificate, edit the fields, and download the PDF. That playground is rate-limited, doesn’t accept raw HTML or URLs, and adds a “Made with getpdf” footer. The API, once you have a free key, is less restricted.
There’s a short walkthrough on the same page at getpdf.dev/#demo.
What you get
- Five starter templates: invoice, receipt, certificate, report, and letter
- Or your own Handlebars / HTML template and JSON data
- Sandboxed Chromium: fresh context per job, JavaScript off by default
- Localhost, private IPs, link-local metadata addresses, and
file://URLs are blocked - The PDF is streamed back and is not stored
- Failed renders don’t count toward your monthly quota
- Free plan: 100 documents/month. Paid plans start at $12/month for 3,000 documents, with a 3-day free trial that auto-renews until you cancel
An MCP server for AI agents is coming, but the package is not on npm yet. Use the REST API from your app or agent for now. We’ll show the planned config at the end so you know what’s coming.
Try it in the browser first
Open https://getpdf.dev/#try, choose Invoice, and hit render. That’s the whole point of this post: you can play with a real PDF before you write any code.
When you’re ready to call the API, create a free key from the site and use Authorization: Bearer df_…. Base URL is https://getpdf.dev.
curl
curl -X POST https://getpdf.dev/v1/render \
-H "Authorization: Bearer $KEY" \
-H "content-type: application/json" \
-d '{
"template_id": "invoice",
"data": {
"number": "INV-1001",
"currency": "$",
"from": {"name": "Acme Studio LLC"},
"to": {"name": "Globex Corp"},
"items": [
{"description": "Design work", "qty": 10, "price": 120},
{"description": "Hosting", "qty": 1, "price": 49}
]
},
"options": {"format": "A4", "landscape": false}
}' \
-o invoice.pdf
You can also send an inline template instead of a starter id:
curl -X POST https://getpdf.dev/v1/render \
-H "Authorization: Bearer $KEY" \
-H "content-type: application/json" \
-d '{"template":"<h1>Hello {{name}}</h1>","data":{"name":"World"}}' \
-o hello.pdf
Helpers on the starters include {{money v currency}}, {{mul a b}}, and {{sum items "qty" "price"}}. Custom templates are private to your key (up to 50 per key).
Node.js
import { writeFile } from "node:fs/promises";
const res = await fetch("https://getpdf.dev/v1/render", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GETPDF_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
template_id: "invoice",
data: {
number: "INV-1001",
currency: "$",
items: [{ description: "Design work", qty: 10, price: 120 }],
},
options: { format: "Letter" },
}),
});
if (!res.ok) {
throw new Error(`${res.status} ${await res.text()}`);
}
await writeFile("invoice.pdf", Buffer.from(await res.arrayBuffer()));
console.log("usage", res.headers.get("x-docforge-usage"));
Python
import os
import requests
response = requests.post(
"https://getpdf.dev/v1/render",
headers={"Authorization": f"Bearer {os.environ['GETPDF_KEY']}"},
json={
"template_id": "invoice",
"data": {
"number": "INV-1001",
"currency": "$",
"items": [{"description": "Design work", "qty": 10, "price": 120}],
},
"options": {"format": "Letter"},
},
timeout=30,
)
response.raise_for_status()
open("invoice.pdf", "wb").write(response.content)
print("usage", response.headers.get("x-docforge-usage"))
You can also create a template once and reuse it:
curl -X POST https://getpdf.dev/v1/templates -H "Authorization: Bearer $KEY" -H "content-type: application/json" -d '{"id":"quote","name":"Quote","source":"<h1>Quote for {{client}}</h1>","sample":{"client":"Initech"}}'
Then render with "template_id": "quote". Starter ids (invoice, receipt, and so on) are read-only. Other keys cannot see your templates.
Useful errors: 400 bad input, 401 bad key, 402 monthly quota reached, 504 render timeout. Input is capped at 2 MB, output at 20 MB, and each render at 20 seconds.
MCP setup (not published yet)
A stdio MCP server is planned with tools to list_templates, get_template, create_template, update_template, delete_template, and render_pdf. The getpdf-mcp package is not on npm, and there is no public GitHub repo yet, so npx -y getpdf-mcp will not work. If you want PDFs from an agent today, have the agent call POST /v1/render with your key.
When the package is published, the client config is expected to look like this (do not install it yet):
{
"mcpServers": {
"getpdf": {
"command": "npx",
"args": ["-y", "getpdf-mcp"],
"env": { "DOCFORGE_API_KEY": "df_..." }
}
}
}
We’ll update the docs at getpdf.dev/docs.html when that ships.
Try it
Start in the playground, no signup: https://getpdf.dev/#try. If you want a key for your app, the free plan is 100 documents a month. Paid plans have a 3-day free trial, then $12, $39, or $79 a month until you cancel.
If you try it, we’d like to know whether the starter invoice is close enough to use as-is, or whether you immediately need your own template.
Disclosure: this post is from the getpdf team.
Top comments (0)