How to generate a PDF invoice from HTML — one API call
Turn an HTML invoice into a pixel-perfect PDF with a single POST request. Real curl and Node.js examples using the PDFInvoiceAPI /v1/render endpoint.
Generating a PDF invoice should not require a headless-Chrome cluster, a print stylesheet you maintain forever, or a weekend wrestling with a PDF library’s box model. If you can write HTML, you already have the layout engine — the browser. PDFInvoiceAPI gives you that engine behind one HTTP endpoint: POST your HTML, get application/pdf back.
This post walks through the core use case end to end: an HTML invoice in, a branded PDF out, in a single API call.
The one endpoint
Everything happens at POST https://api.pdfinvoiceapi.com/v1/render. You authenticate with a Bearer API key, send JSON, and the response body is the PDF — raw bytes, content-type: application/pdf. There’s no job to poll and no file URL to fetch; you stream the result straight to a file, a browser download, or object storage.
You’ll need an API key first. Sign up free (25 render credits, no card), then create a key in Dashboard → API keys — it’s shown once, so copy it somewhere safe.
The simplest possible render: raw HTML
Send your invoice markup in the html field. That’s the whole request:
curl https://api.pdfinvoiceapi.com/v1/render \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Invoice #1024</h1><p>Bill to: Globex LLC</p><p>Total: €1,240.00</p>",
"pdf": { "format": "A4", "printBackground": true }
}' \
-o invoice.pdf
You now have invoice.pdf on disk. The pdf object controls print options — format ("A4", "Letter", …), landscape, printBackground, margin, and scale. It defaults to A4 with backgrounds on, which is what you want for an invoice with a coloured header.
The same thing in Node.js
Because the response is binary, write the arrayBuffer() straight to a file:
import { writeFile } from "node:fs/promises";
const invoiceHtml = `
<style>
body { font-family: system-ui, sans-serif; color: #1a1a23; }
.total { font-size: 1.4rem; font-weight: 700; }
</style>
<h1>Invoice #1024</h1>
<p>Bill to: Globex LLC</p>
<p class="total">Total: €1,240.00</p>
`;
const res = await fetch("https://api.pdfinvoiceapi.com/v1/render", {
method: "POST",
headers: {
Authorization: "Bearer sk_live_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
html: invoiceHtml,
pdf: { format: "A4", printBackground: true },
}),
});
if (!res.ok) throw new Error(await res.text());
await writeFile("invoice.pdf", Buffer.from(await res.arrayBuffer()));
Style it with ordinary CSS — @page rules, web fonts, flexbox, a coloured header bar. If it renders in Chrome, it renders in your PDF.
Don’t concatenate strings: use a template
Building HTML by string concatenation is how you ship an invoice with an unescaped customer name. Instead, send a template plus a data object and let the API merge them. Placeholders use {{ }} (HTML-escaped, the safe default), {{{ }}} (raw, for HTML you trust), and dotted paths for nested values:
curl https://api.pdfinvoiceapi.com/v1/render \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"template": "<h1>Invoice {{number}}</h1><p>Bill to: {{billTo.name}}</p><p>Total: {{total}}</p>",
"data": {
"number": "INV-1024",
"billTo": { "name": "Globex LLC" },
"total": "€1,240.00"
},
"pdf": { "format": "A4", "printBackground": true }
}' \
-o invoice.pdf
The merge engine is deliberately simple — {{ }}, {{{ }}}, and dotted paths, no loops or conditionals. For a line-item table, render the rows in your own code (you already have the data in a list) and pass the finished HTML to template, or build the whole document and send it as html. Keeping the engine loop-free keeps renders fast and predictable.
If you reuse the same layout for every invoice, save it once in the dashboard and render it by its stored id (tpl_…) instead of resending the markup each time — pass that id as template and only send data.
What you get back
A 200 with content-type: application/pdf and the PDF bytes as the body. Each successful render costs one credit. On a failure you get a JSON error body instead of a PDF, so check res.ok before writing the file.
That’s the whole loop: HTML in, PDF out, one call. From here:
- Read the full render reference for every
pdfoption. - Learn the template + data merge rules in depth.
- Start free and render your first invoice in the next five minutes.