Skip to content
TryDocify

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 partDocument feature
LogoAn image block (base64 PNG, JPEG or WebP)
Invoice number and datestitle and subtitle
Customer, terms, referencesA keyValue block with 2 columns
Line itemsA table; rows flow across pages and the header repeats
Subtotal, tax, totalTable footer rows; the last is styled as the grand total
Payment instructionsA note block
Seller address, page numbersThe running footer with showPageNumbers
PDF propertiesinfo (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.

JavaScript · invoice.mjs
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.accentColor colors the title, table header and totals. theme.fontSize (7–16) and theme.margin adjust density.
  • Paper size: pageSize supports 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: fileName is sanitized and returned in Content-Disposition, so the download is named invoice-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 429 after Retry-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": true while 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.