Generate a PDF invoice from HTML via API
Invoices, receipts, contracts, and reports almost always start life as HTML and need to end up as a PDF. This guide shows how to turn an HTML template into a clean PDF from your own code with a single request, with copy-paste examples in curl, Python, and Node.js.
Why render PDFs from HTML
You already know HTML and CSS, so it is the fastest way to design a document. You lay out the invoice once as a template, drop in the customer's data, and hand the markup to an API that returns a PDF. There is no headless browser to install, no font packages to manage on your server, and no PDF drawing library to learn.
FileAlloy exposes this as POST /v1/html-to-pdf. You send exactly one of two things: a url to render a live page, or a block of html you built yourself. It returns the PDF bytes. Input and output stay in memory for the request and are dropped as soon as the response is sent, so customer data is never stored.
Rendering a live URL
The simplest call points at a public page and saves the result. This is handy for snapshotting a report or a receipt page you already serve:
curl -X POST https://api.filealloy.com/v1/html-to-pdf \
-H "X-API-Key: fa_live_your_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}' \
-o page.pdf For invoices you usually want full control over the layout, so the rest of this guide builds the HTML in code and sends that instead.
The invoice template
Keep the styling inline or in a single <style> block so the document is self-contained. Here is a small invoice template to start from:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<style>
body { font-family: Arial, sans-serif; color: #17171a; padding: 48px; }
h1 { font-size: 22px; margin: 0 0 4px; }
.muted { color: #6b7280; font-size: 13px; }
table { width: 100%; border-collapse: collapse; margin-top: 28px; }
th, td { text-align: left; padding: 8px 0; border-bottom: 1px solid #e5e7eb; }
.total { text-align: right; font-size: 18px; font-weight: bold; margin-top: 20px; }
</style>
</head>
<body>
<h1>Invoice INV-1042</h1>
<p class="muted">Issued 14 September 2026 · Due 28 September 2026</p>
<table>
<tr><th>Description</th><th>Amount</th></tr>
<tr><td>Consulting, September</td><td>1,000.00 EUR</td></tr>
<tr><td>Support retainer</td><td>250.00 EUR</td></tr>
</table>
<p class="total">Total: 1,250.00 EUR</p>
</body>
</html> Everything is plain HTML and CSS. Use a print-friendly font stack, real tables for line items, and absolute units where it matters. When you are happy with how it looks in a browser, move it into your code and fill the values from your data.
Python
Build the markup with an f-string, then post it as JSON. Note the doubled braces in the CSS, which is how an f-string escapes a literal brace:
import requests
invoice = {"number": "INV-1042", "client": "Acme Corp", "total": "1,250.00 EUR"}
html = f"""
<!doctype html>
<html>
<head><meta charset="utf-8" /><style>
body {{ font-family: Arial, sans-serif; color: #17171a; padding: 48px; }}
h1 {{ font-size: 22px; }}
.total {{ font-size: 18px; font-weight: bold; margin-top: 24px; }}
</style></head>
<body>
<h1>Invoice {invoice['number']}</h1>
<p>Billed to: {invoice['client']}</p>
<p class="total">Total: {invoice['total']}</p>
</body>
</html>
"""
resp = requests.post(
"https://api.filealloy.com/v1/html-to-pdf",
headers={"X-API-Key": "fa_live_your_key"},
json={"html": html},
)
resp.raise_for_status()
with open("invoice.pdf", "wb") as out:
out.write(resp.content)
print("Saved invoice.pdf") Node.js
On Node 18 or newer, fetch is built in. A template literal makes the HTML easy to fill:
import { writeFile } from "node:fs/promises";
const invoice = { number: "INV-1042", client: "Acme Corp", total: "1,250.00 EUR" };
const html = `
<!doctype html>
<html>
<head><meta charset="utf-8" /><style>
body { font-family: Arial, sans-serif; color: #17171a; padding: 48px; }
h1 { font-size: 22px; }
.total { font-size: 18px; font-weight: bold; margin-top: 24px; }
</style></head>
<body>
<h1>Invoice ${invoice.number}</h1>
<p>Billed to: ${invoice.client}</p>
<p class="total">Total: ${invoice.total}</p>
</body>
</html>`;
const res = await fetch("https://api.filealloy.com/v1/html-to-pdf", {
method: "POST",
headers: {
"X-API-Key": "fa_live_your_key",
"Content-Type": "application/json",
},
body: JSON.stringify({ html }),
});
if (!res.ok) throw new Error(`HTML to PDF failed: ${res.status}`);
const pdf = Buffer.from(await res.arrayBuffer());
await writeFile("invoice.pdf", pdf);
console.log("Saved invoice.pdf"); Handling errors
The API uses standard HTTP status codes. A 400 means you sent neither url nor html, or you sent a blocked address such as a localhost or private URL. A 401 means the key is missing or wrong, and a 429 means you are over your plan's monthly quota. Check the status before you write the file, as both examples above do, so you never save an error page as a .pdf.
Tips for print-ready PDFs
A few things make the output look professional. Set page margins and size with a CSS @page rule. Prefer web-safe or embedded fonts so the result is the same on every machine. Use page-break-inside: avoid on rows and totals so a table does not split awkwardly across pages. And because the render is deterministic, the same template and data always produce the same PDF, which means you can safely cache invoices and diff them in tests.
Summary
One JSON call turns your HTML into a PDF, with no browser or drawing library in your stack. Design the document once, fill it with data, and let the API return the file. The same request shape works for receipts, contracts, packing slips, and reports, so a single template engine in your app can produce every document type you need.