Use case
Invoice PDF generation API
Turn invoice data into professional, multi-page PDF invoices: line items, tax, totals, payment details and a logo, mapped from your own data model in one function.
Updated · Every code sample on this page is run against the API before publishing.
What an invoice PDF needs
Invoices look simple until they don't fit on one page. A production invoice generator has to handle a variable number of line items, keep totals together at the end, repeat the column headings when the table continues, number the pages, and print the seller's details on every page. Each of these maps to one TryDocify feature:
| Invoice part | Document feature |
|---|---|
| Logo | An image block (base64 PNG, JPEG or WebP) |
| Invoice number and dates | title and subtitle |
| Customer, terms, references | A keyValue block with 2 columns |
| Line items | A table; rows flow across pages and the header repeats |
| Subtotal, tax, total | Table footer rows; the last is styled as the grand total |
| Payment instructions | A note block |
| Seller address, page numbers | The running footer with showPageNumbers |
| PDF properties | info (author, subject) and fileName |
Map your data in one function
Keep invoice layout in a pure function that takes your invoice record and returns a document. It has no I/O, so it is easy to unit test, and the API call stays a single fetch. This example is Node.js; the same mapping works in any language.
import { readFile, writeFile } from "node:fs/promises";
/** Maps your invoice data to a TryDocify document. Pure function: easy to unit test. */
export function buildInvoiceDocument(invoice, { logoPng } = {}) {
const money = new Intl.NumberFormat(invoice.locale, { style: "currency", currency: invoice.currency });
const subtotal = invoice.lines.reduce((sum, line) => sum + line.quantity * line.unitPrice, 0);
const tax = Math.round(subtotal * invoice.taxRate * 100) / 100;
return {
test: process.env.NODE_ENV !== "production",
fileName: `invoice-${invoice.number}.pdf`,
title: `Invoice ${invoice.number}`,
subtitle: `Issued ${invoice.issued} · Due ${invoice.due}`,
header: { left: invoice.seller.name, right: invoice.number },
footer: { text: `${invoice.seller.name} · ${invoice.seller.address}`, showPageNumbers: true },
info: { author: invoice.seller.name, subject: `Invoice ${invoice.number}` },
blocks: [
...(logoPng ? [{ type: "image", src: `data:image/png;base64,${logoPng.toString("base64")}`, width: 96 }] : []),
{
type: "keyValue",
columns: 2,
items: [
{ key: "Billed to", value: invoice.customer.name },
{ key: "Invoice no.", value: invoice.number },
{ key: "Email", value: invoice.customer.email },
{ key: "Terms", value: invoice.terms },
],
},
{
type: "table",
columns: [
{ header: "Description", width: 4 },
{ header: "Qty", align: "right" },
{ header: "Unit price", align: "right", width: 2 },
{ header: "Amount", align: "right", width: 2 },
],
rows: invoice.lines.map((line) => [
line.description,
line.quantity,
money.format(line.unitPrice),
money.format(line.quantity * line.unitPrice),
]),
footer: [
["Subtotal", "", "", money.format(subtotal)],
[`Tax (${invoice.taxRate * 100}%)`, "", "", money.format(tax)],
["Total due", "", "", money.format(subtotal + tax)],
],
},
{ type: "note", tone: "info", title: "Payment", text: invoice.paymentInstructions },
],
};
}
// Example: 40 line items flow onto a second page, and the table header repeats.
const invoice = {
number: "INV-2026-0042",
issued: "3 Nov 2026",
due: "3 Dec 2026",
terms: "Net 30",
locale: "en-US",
currency: "USD",
taxRate: 0.2,
seller: { name: "Acme Studio", address: "221B Baker Street, London" },
customer: { name: "Globex Corporation", email: "[email protected]" },
paymentInstructions: "Bank transfer to IBAN GB33 BUKB 2020 1555 5555 55. Reference INV-2026-0042.",
lines: Array.from({ length: 40 }, (_, i) => ({ description: `Support hours, week ${i + 1}`, quantity: 6, unitPrice: 85 })),
};
const logoPng = await readFile("logo.png").catch(() => undefined); // optional
const response = await fetch("https://api.trydocify.com/api/v1/generate", {
method: "POST",
headers: { "X-API-Key": process.env.TRYDOCIFY_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify(buildInvoiceDocument(invoice, { logoPng })),
});
if (!response.ok) {
const problem = await response.json();
throw new Error(`${problem.code}: ${problem.detail}`);
}
await writeFile(`invoice-${invoice.number}.pdf`, Buffer.from(await response.arrayBuffer()));
console.log(`Saved invoice-${invoice.number}.pdf, ${response.headers.get("X-Page-Count")} pages`);Money is formatted with Intl.NumberFormat for the invoice's locale and currency before it is sent. The API prints values exactly as given, so rounding and tax rules stay in your code, where your accounting logic already lives.
Multi-page invoices
The example has 40 line items, so it runs to a second page. The table continues with its header row repeated, the totals follow the last item, and the footer shows "Page 1 of 2" and "Page 2 of 2". There is no page-break logic in your code. Tables can hold up to 20,000 rows, which covers statements and usage-based invoices too.
Details that matter
- Brand color:
theme.accentColorcolors the title, table header and totals.theme.fontSize(7–16) andtheme.marginadjust density. - Paper size:
pageSizesupports A4 (default), Letter, Legal, A3 and A5, in portrait or landscape. Use Letter for US customers. - Logo: keep it under 2 MB; a PNG of a few kilobytes renders sharply at 96 points wide.
- File name:
fileNameis sanitized and returned inContent-Disposition, so the download is namedinvoice-INV-2026-0042.pdf. - Privacy: invoices contain customer data. TryDocify renders in memory and never stores the document or the PDF; only usage counters are kept.
Automating invoice runs
For a monthly billing run, generate invoices in a background job rather than in a web request: loop over the invoices, render each one, and email or archive the result. A few practical points:
- Each key sustains 120 requests a minute with bursts of 30. Run a handful in parallel and retry
429afterRetry-After. - Each successful invoice uses one credit; failed or rejected requests are free. Starter includes 1,000 PDFs a month and Pro 10,000.
- Use
"test": truewhile you tune the layout against real data, then switch it off for the run.
Prefer another language? The same invoice works from C# and Python, and the JSON tutorial has it as a plain JSON file for cURL.
Try it with your own data
Every plan includes 200 free test renders a day. Add "test": true while you build, and remove it when you go live.