Foliant

Templates / Invoice

Invoice

Sender block, addressee, line items from JSON, totals with VAT, payment footer. The document most APIs are bought for.

1 page, A4. Rendered in 81 ms. 15 inputs, all optional.

Download .typ Open preview

Rendered Invoice template, first page
First page, rendered from the source below with the sample inputs.

Inputs

Everything variable comes in through sys.inputs. Missing keys fall back to sensible defaults, so an agent can send only what it knows.

KeySample value
bankBerliner Volksbank, IBAN DE12 1009 0000 1234 5678 90
currencyEUR
customerNorthwind Traders
customer_addressKastanienallee 7, 10435 Berlin
date2026-09-28
due2026-10-28
items
JSON, 240 chars
[
  {
    "name": "Design system audit",
    "qty": "1",
    "unit": "2 400.00",
    "total": "2 400.00"
  },
  {
    "name": "Component library, phase 1",
    "qty": "32 h",
    "unit": "120.00",
    "total": "3 840.00"
  },
  {
    "name": "Accessibility review",
    "qty": "1",
    "unit": "900.00",
    "total": "900.00"
  }
]
logologo.png
no2026-0042
senderMeridian Studio GmbH
sender_addressRosenthaler Str. 41, 10178 Berlin
subtotal7 140.00
total8 496.60
vat1 356.60
vat_rate19

Render it

After copying the template into your account:

curl -X POST https://api.foliant.dev/v1/render \
  -H "Authorization: Bearer $FOLIANT_API_KEY" \
  -H "Content-Type: application/json" \
  -o invoice.pdf -d @- <<'JSON'
{ "template": "invoice.typ",
  "inputs": {
    "bank": "Berliner Volksbank, IBAN DE12 1009 0000 1234 5678 90",
    "currency": "EUR",
    "customer": "Northwind Traders",
    "customer_address": "Kastanienallee 7, 10435 Berlin",
    "date": "2026-09-28",
    "due": "2026-10-28",
    "items": "[{\"name\":\"Design system audit\",\"qty\":\"1\",\"unit\":\"2 400.00\",\"total\":\"2 400.00\"},{\"name\":\"Component library, phase 1\",\"qty\":\"32 h\",\"unit\":\"120.00\",\"total\":\"3 840.00\"},{\"name\":\"Accessibility review\",\"qty\":\"1\",\"unit\":\"900.00\",\"total\":\"900.00\"}]",
    "logo": "logo.png",
    "no": "2026-0042",
    "sender": "Meridian Studio GmbH",
    "sender_address": "Rosenthaler Str. 41, 10178 Berlin",
    "subtotal": "7 140.00",
    "total": "8 496.60",
    "vat": "1 356.60",
    "vat_rate": "19"
  } }
JSON

Through MCP, render_document with template: "invoice.typ" and the same inputs; get_template or fetch lists the keys.

Source

template.typ, 33 lines
// Invoice. Data comes in through sys.inputs; line items as a JSON string.
#let input(k, default: "") = sys.inputs.at(k, default: default)
#let items = json(bytes(input("items", default: "[]")))
#let cur = input("currency", default: "EUR")
#let logo = input("logo")

#set page(paper: "a4", margin: (top: 22mm, bottom: 24mm, x: 22mm),
  footer: context [#set text(size: 8.5pt, fill: luma(110)); #input("sender") · #input("sender_address") #h(1fr) #counter(page).display() / #counter(page).final().first()])
#set text(font: "Libertinus Serif", size: 10.5pt, fill: rgb("1a1917"))

#grid(columns: (1fr, auto), align: (left + top, right + top),
  if logo != "" { image(logo, width: 38mm) } else { text(size: 16pt, weight: 600)[#input("sender")] },
  [#text(size: 20pt, weight: 600)[Invoice] \ #text(fill: luma(110))[No. #input("no", default: "0001")]])
#v(10mm)
#grid(columns: (1fr, 1fr), gutter: 10mm,
  [#text(size: 8.5pt, fill: luma(110), tracking: 0.6pt)[BILL TO] \ *#input("customer", default: "Customer")* \ #input("customer_address")],
  [#text(size: 8.5pt, fill: luma(110), tracking: 0.6pt)[DETAILS] \
   #grid(columns: (auto, 1fr), column-gutter: 6mm, row-gutter: 3pt,
     [Date], [#input("date")], [Due], [#input("due")], [Reference], [#input("no")])])
#v(10mm)
#table(columns: (1fr, auto, auto, auto), align: (left, right, right, right), inset: (y: 7pt),
  stroke: (x, y) => if y == 0 { (bottom: 0.7pt) } else { (bottom: 0.3pt + luma(200)) },
  [_Item_], [_Qty_], [_Unit_], [_Total_],
  ..items.map(i => (i.name, i.qty, i.unit, i.total)).flatten())
#v(6mm)
#align(right)[#grid(columns: (auto, 34mm), align: (left, right), row-gutter: 6pt,
  [Subtotal], [#input("subtotal") #cur],
  [VAT #input("vat_rate", default: "0") %], [#input("vat") #cur],
  [#line(length: 100%, stroke: 0.7pt)], [#line(length: 100%, stroke: 0.7pt)],
  [*Total due*], [*#input("total") #cur*])]
#v(1fr)
#text(size: 9pt, fill: luma(110))[Payable within 30 days. #input("bank"). Reference #input("no").]