← All posts

Generating invoices in code without the pain

Generating invoices in code usually means HTML templates, headless Chrome, and broken page breaks. Here is a simpler way: a hosted API your coding agent can wire in for you.

invoicespdfapihow-to

Almost every app ends up needing to make an invoice PDF. It sounds like a small job. It is not. The first version works, then a customer has twenty line items, the table runs onto a second page, the header disappears, the totals float off on their own, and you spend a day fixing CSS you never wanted to write.

The pain is not the invoice. The pain is everything around turning it into a clean PDF. Here is how to skip that part.

What makes invoices hard in code

Most teams reach for the same approach: build an HTML template, fill it with data, then run headless Chrome to print it to PDF. It works for a one-page invoice. The problems start when the content grows:

  • The table header shows on page one but not on page two.
  • A row gets cut in half across the page break.
  • The totals block lands alone at the top of a new page, away from the table.
  • Page numbers are missing, or you hand-roll them with brittle CSS.
  • A new font or a long company name shifts the whole layout.

None of this is about your business logic. It is layout plumbing, and you maintain it forever. Every invoice change risks breaking the pagination again.

Describe the invoice instead of drawing it

kove takes a different path. You do not write HTML or CSS. You describe the invoice as a JSON document: what goes in it, in what order. kove decides how it looks and how it paginates.

{
"page": { "size": "A4" },
"header": { "text": "Acme Inc." },
"footer": { "pageNumbers": "Page {page} of {pages}" },
"body": [
{ "type": "heading", "text": "Invoice F-2026-014" },
{ "type": "fields", "items": [
{ "label": "Bill to", "value": "Globex LLC" },
{ "label": "Issue date", "value": "2026-02-11" },
{ "label": "Due date", "value": "2026-03-13" }
] },
{ "type": "table",
"columns": ["Description", "Qty", "Unit", "Amount"],
"rows": [
["Consulting, January", "40", "$120.00", "$4,800.00"],
["Cloud hosting", "1", "$84.00", "$84.00"],
["Support plan", "1", "$199.00", "$199.00"]
] },
{ "type": "totals", "lines": [
{ "label": "Subtotal", "value": "$5,083.00" },
{ "label": "Tax (21%)", "value": "$1,067.43" },
{ "label": "Total", "value": "$6,150.43" }
] }
]
}

Read it top to bottom and you can see the whole invoice. The table can have three rows or three hundred. kove repeats the header on every page, keeps rows whole, keeps the totals with the table, and adds the page numbers. You do not touch any of that.

Render it from your app

kove is a hosted document API. When the invoice is part of a product flow, like sending it after a payment, call the API with your API key:

Terminal window
curl -X POST https://api.kove.dev/v1/documents \
-H "Authorization: Bearer $KOVE_API_KEY" \
-H "Content-Type: application/json" \
-d @invoice.json \
-o invoice.pdf

You get back a finished PDF, ready to store or email. There is nothing to set up, no browser to run, no rendering server to keep alive. We run all of that. You run nothing.

You do not even have to write this call by hand. kove ships AI-friendly docs, an llms.txt index and an OpenAPI spec, so you can tell your coding agent “add PDF invoices to my checkout” and it reads the spec, wires the API into your code, and hands you a diff to review.

Or render it locally, for free

If you are building a script, a cron job, or just testing, the CLI runs on your own computer and is free with no limits and no account.

Terminal window
npx kove validate invoice.json
npx kove render invoice.json -o invoice.pdf

Validate first to catch a typo before you render. This is the fast way to iterate on the layout of your invoices while you build, and you can ship the same JSON to the API later with no changes.

Build the JSON the way you already build data

The nice part of a declarative document is that producing it is just data work. You already have the customer, the line items, and the totals in your code. Map them into the document shape and you are done:

const invoice = {
page: { size: "A4" },
footer: { pageNumbers: "Page {page} of {pages}" },
body: [
{ type: "heading", text: `Invoice ${number}` },
{ type: "fields", items: [
{ label: "Bill to", value: customer.name },
{ label: "Due date", value: dueDate },
] },
{ type: "table",
columns: ["Description", "Qty", "Unit", "Amount"],
rows: items.map((i) => [i.name, i.qty, i.unit, i.amount]) },
{ type: "totals", lines: [
{ label: "Subtotal", value: subtotal },
{ label: "Tax", value: tax },
{ label: "Total", value: total },
] },
],
}

No template engine. No headless browser in your deploy. No CSS for page breaks. Just the data you already have, in a shape kove understands.

The payoff

Invoices change. Tax lines get added, branding gets updated, a customer has a huge order. With a declarative document, those are small data edits, not layout rewrites. The hard parts, repeating headers, whole rows, totals that stay put, page numbers, fonts that look right, are handled for you and stay handled.

Try it local and free with npx kove render, then point the same JSON at the API when you are ready to ship.