Generate PDF Invoices with a Simple API Call
Generating PDF invoices is a common requirement for SaaS apps, e-commerce platforms, and freelance tools. Most solutions involve a PDF library, coordinate-based layouts, or a headless browser. The Apixies HTML to PDF API offers a simpler path: build your invoice as HTML, inject your data, POST it to the API, and receive a ready-to-send PDF. You get pixel-perfect control over the layout using CSS, with zero server-side dependencies.
Why HTML is the best format for invoices
PDF libraries force you to place elements at exact coordinates and define fonts manually. HTML and CSS give you a better toolkit:
- Flexbox and tables for aligning columns and line items.
@pagerules for controlling paper size, margins, and page breaks.- Web fonts and inline styles for brand-consistent typography.
If you can build a web page, you can build an invoice. The API renders your HTML with a real browser engine, so the output matches what you see in a browser.
The invoice template
Here is a complete HTML invoice template with a company header, billing details, an itemized table, and tax calculation. Customize the styles to match your brand.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
@page { size: A4; margin: 20mm; }
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Helvetica Neue', Arial, sans-serif; color: #1a202c; font-size: 14px; }
.invoice-header { display: flex; justify-content: space-between; margin-bottom: 40px; }
.company-name { font-size: 24px; font-weight: 700; color: #2b6cb0; }
.company-details { font-size: 12px; color: #718096; line-height: 1.6; }
.invoice-title { text-align: right; }
.invoice-title h2 { font-size: 28px; color: #2d3748; text-transform: uppercase; }
.invoice-number { font-size: 14px; color: #718096; margin-top: 4px; }
.billing { display: flex; justify-content: space-between; margin-bottom: 30px; }
.billing div { width: 48%; }
.billing h3 { font-size: 11px; text-transform: uppercase; color: #a0aec0; margin-bottom: 8px; }
.billing p { line-height: 1.6; color: #4a5568; }
table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }
thead th { background: #edf2f7; padding: 10px 12px; text-align: left; font-size: 12px; text-transform: uppercase; color: #4a5568; }
tbody td { padding: 10px 12px; border-bottom: 1px solid #e2e8f0; }
.text-right { text-align: right; }
.totals { width: 280px; margin-left: auto; }
.totals-row { display: flex; justify-content: space-between; padding: 6px 0; font-size: 14px; color: #4a5568; }
.totals-row.grand-total { border-top: 2px solid #2b6cb0; padding-top: 10px; margin-top: 6px; font-size: 18px; font-weight: 700; color: #1a202c; }
.footer { margin-top: 50px; padding-top: 20px; border-top: 1px solid #e2e8f0; font-size: 12px; color: #a0aec0; text-align: center; }
</style>
</head>
<body>
<div class="invoice-header">
<div>
<div class="company-name">Acme Corp</div>
<div class="company-details">
123 Business Ave, Suite 400<br>
San Francisco, CA 94107<br>
billing@acmecorp.com
</div>
</div>
<div class="invoice-title">
<h2>Invoice</h2>
<div class="invoice-number">INV-2026-0042</div>
<div class="invoice-number">Date: February 20, 2026</div>
<div class="invoice-number">Due: March 22, 2026</div>
</div>
</div>
<div class="billing">
<div>
<h3>Bill To</h3>
<p><strong>Jane Martinez</strong><br>
Martinez Design Studio<br>
456 Oak Street<br>
Portland, OR 97201<br>
jane@martinezdesign.com</p>
</div>
<div>
<h3>Payment Details</h3>
<p>Bank: First National Bank<br>
Account: 9283-0192-4455<br>
Routing: 021000021<br>
Reference: INV-2026-0042</p>
</div>
</div>
<table>
<thead>
<tr>
<th>Description</th>
<th>Qty</th>
<th>Unit Price</th>
<th class="text-right">Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td>Website redesign -- homepage and landing pages</td>
<td>1</td>
<td>$3,500.00</td>
<td class="text-right">$3,500.00</td>
</tr>
<tr>
<td>Custom illustration pack (8 illustrations)</td>
<td>8</td>
<td>$150.00</td>
<td class="text-right">$1,200.00</td>
</tr>
<tr>
<td>SEO audit and optimization report</td>
<td>1</td>
<td>$800.00</td>
<td class="text-right">$800.00</td>
</tr>
</tbody>
</table>
<div class="totals">
<div class="totals-row"><span>Subtotal</span><span>$5,500.00</span></div>
<div class="totals-row"><span>Tax (8.5%)</span><span>$467.50</span></div>
<div class="totals-row grand-total"><span>Total Due</span><span>$5,967.50</span></div>
</div>
<div class="footer">
Thank you for your business. Payment is due within 30 days of the invoice date.
</div>
</body>
</html>
The @page rule sets A4 paper size with 20mm margins, and CSS handles all alignment without JavaScript.
Injecting dynamic data
In a real application, you build the HTML string from your database records using string interpolation or a template engine.
Python example with f-strings
import requests
import os
def generate_invoice_pdf(invoice):
html = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
@page {{ size: A4; margin: 20mm; }}
body {{ font-family: Helvetica, sans-serif; font-size: 14px; color: #1a202c; }}
.header {{ display: flex; justify-content: space-between; margin-bottom: 30px; }}
.company {{ font-size: 22px; font-weight: bold; color: #2b6cb0; }}
table {{ width: 100%; border-collapse: collapse; margin: 20px 0; }}
th {{ background: #edf2f7; padding: 8px; text-align: left; font-size: 12px; text-transform: uppercase; }}
td {{ padding: 8px; border-bottom: 1px solid #e2e8f0; }}
.text-right {{ text-align: right; }}
.total {{ font-size: 18px; font-weight: bold; text-align: right; margin-top: 20px; }}
</style>
</head>
<body>
<div class="header">
<div class="company">Acme Corp</div>
<div>Invoice {invoice['number']}<br>{invoice['date']}</div>
</div>
<p><strong>Bill to:</strong> {invoice['client_name']}</p>
<table>
<tr><th>Description</th><th>Qty</th><th class="text-right">Amount</th></tr>
{''.join(f'<tr><td>{item["desc"]}</td><td>{item["qty"]}</td><td class="text-right">${item["amount"]:.2f}</td></tr>' for item in invoice['items'])}
</table>
<div class="total">Total: ${invoice['total']:.2f}</div>
</body>
</html>"""
response = requests.post(
"https://apixies.io/api/v1/html-to-pdf",
headers={"X-API-Key": os.environ["APIXIES_KEY"]},
data={"html": html},
)
response.raise_for_status()
return response.content
# Usage
invoice_data = {
"number": "INV-2026-0042",
"date": "February 20, 2026",
"client_name": "Jane Martinez",
"items": [
{"desc": "Website redesign", "qty": 1, "amount": 3500.00},
{"desc": "Illustration pack", "qty": 8, "amount": 1200.00},
],
"total": 4700.00,
}
pdf_bytes = generate_invoice_pdf(invoice_data)
with open("invoice.pdf", "wb") as f:
f.write(pdf_bytes)
print("Invoice saved as invoice.pdf")
JavaScript example with template literals
const fs = require('fs');
async function generateInvoice(invoice) {
const rows = invoice.items
.map(i => `<tr><td>${i.desc}</td><td>${i.qty}</td><td class="text-right">$${i.amount.toFixed(2)}</td></tr>`)
.join('\n');
const html = `<!DOCTYPE html>
<html>
<head>
<style>
@page { size: A4; margin: 20mm; }
body { font-family: Helvetica, sans-serif; font-size: 14px; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th { background: #edf2f7; padding: 8px; text-align: left; }
td { padding: 8px; border-bottom: 1px solid #e2e8f0; }
.text-right { text-align: right; }
.total { font-size: 20px; font-weight: bold; text-align: right; margin-top: 20px; }
</style>
</head>
<body>
<h1 style="color:#2b6cb0">Invoice ${invoice.number}</h1>
<p>Date: ${invoice.date} | Client: ${invoice.client}</p>
<table>
<tr><th>Description</th><th>Qty</th><th class="text-right">Amount</th></tr>
${rows}
</table>
<div class="total">Total: $${invoice.total.toFixed(2)}</div>
</body>
</html>`;
const res = await fetch('https://apixies.io/api/v1/html-to-pdf', {
method: 'POST',
headers: {
'X-API-Key': process.env.APIXIES_KEY,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({ html }),
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
return Buffer.from(await res.arrayBuffer());
}
// Usage
const pdf = await generateInvoice({
number: 'INV-2026-0042',
date: 'February 20, 2026',
client: 'Jane Martinez',
items: [
{ desc: 'Website redesign', qty: 1, amount: 3500 },
{ desc: 'SEO audit', qty: 1, amount: 800 },
],
total: 4300,
});
fs.writeFileSync('invoice.pdf', pdf);
console.log('Invoice saved');
Handling page breaks for long invoices
Invoices with many line items may span multiple pages. Use CSS to control where breaks occur:
<style>
tr { page-break-inside: avoid; }
.footer { page-break-before: avoid; }
.section { page-break-after: always; }
</style>
The page-break-inside: avoid rule prevents a row from splitting across two pages.
Tips for production invoice generation
Store the PDF. Save the binary to cloud storage (S3, GCS) for an immutable record instead of re-generating on every download.
Version your templates. Keep templates in source control so design changes do not affect older invoices.
Validate totals server-side. Calculate subtotals, tax, and totals in your backend, then inject the final numbers into the template.
Attach to emails. Pass the PDF bytes directly to your email library as an attachment -- no temp file needed.
Free tier and getting started
The Apixies free tier includes 75 requests per day with a registered account -- enough for most small businesses and side projects. Anonymous requests are available at 20 per day for quick testing.
Get your free API key and start generating invoices in minutes. See the API documentation for the full parameter reference, the code examples guide for more languages, or browse all tutorials on the guides page.