210 × 297 mm
Invoices
Bold brand invoice
A confident, colourful A4 invoice for studios and product companies: a brand-coloured hero with the amount due, brand colours you set from data, itemised EN 16931 lines, VAT, a pay-by-link QR code and the same data embedded as ZUGFeRD / Factur-X EN 16931 XML.
Opens Dynamic Document API with this template preloaded in a free workspace.
Template language features
- Loops
- Conditionals
- Macros
- Filters
- Currency formatting
- Date formatting
- Number formatting
- QR code
- Repeating table headers
- Header & footer
- Page numbers
Filters such as format_currency and qrcode are described in the template language reference.
Variables
Derived from the sample data: 5 top-level fields, 70 paths in total. Send the same shape as data.
| Path | Type | Example |
|---|---|---|
| invoice | object | |
| invoice.number | string | "FQ-2026-0419" |
| invoice.issue_date | date | "2026-09-18" |
| invoice.due_date | date | "2026-10-18" |
| invoice.currency | string | "EUR" |
| invoice.buyer_reference | string | "LNF-APP-2026" |
| invoice.order_reference | string | "PO-40277" |
| invoice.payment_terms | string | "Net 30 days" |
| invoice.delivery | object | |
| invoice.delivery.date | date | "2026-09-16" |
| invoice.seller | object | |
| invoice.seller.name | string | "Ferrow & Quay Studio Ltd" |
| invoice.seller.trading_name | string | "Ferrow & Quay" |
| invoice.seller.vat_id | string | "IE1234567FA" |
| invoice.seller.legal_registration | object | |
| invoice.seller.legal_registration.id | string | "700419" |
| invoice.seller.electronic_address | object | |
| invoice.seller.electronic_address.id | string | "accounts@ferrowquay.example.com" |
| invoice.seller.electronic_address.scheme | string | "EM" |
| invoice.seller.address | object | |
| invoice.seller.address.line1 | string | "Studio 3, The Ropeworks" |
| invoice.seller.address.line2 | string | "14 Marsh Quay Lane" |
| invoice.seller.address.city | string | "Cork" |
| invoice.seller.address.postcode | string | "T12 X7Q4" |
| invoice.seller.address.country | string | "IE" |
| invoice.seller.contact | object | |
| invoice.seller.contact.name | string | "Studio accounts" |
| invoice.seller.contact.email | string | "accounts@ferrowquay.example.com" |
| invoice.buyer | object | |
| invoice.buyer.name | string | "Lanternfield Health DAC" |
| invoice.buyer.vat_id | string | "IE7654321KA" |
| invoice.buyer.electronic_address | object | |
| invoice.buyer.electronic_address.id | string | "payables@lanternfield.example.com" |
| invoice.buyer.electronic_address.scheme | string | "EM" |
| invoice.buyer.address | object | |
| invoice.buyer.address.line1 | string | "Level 4, Weavers Hall" |
| invoice.buyer.address.line2 | string | "61 Canal Terrace" |
| invoice.buyer.address.city | string | "Dublin" |
| invoice.buyer.address.postcode | string | "D08 R2Y6" |
| invoice.buyer.address.country | string | "IE" |
| invoice.buyer.contact | object | |
| invoice.buyer.contact.name | string | "Niamh Corrigan" |
| invoice.payment | object | |
| invoice.payment.means_code | string | "58" |
| invoice.payment.remittance_information | string | "FQ-2026-0419" |
| invoice.payment.credit_transfers | array<object> · 1 | |
| invoice.payment.credit_transfers[].account_id | string | "IE00EXMP99000012345678" |
| invoice.payment.credit_transfers[].account_name | string | "Ferrow & Quay Studio Ltd" |
| invoice.payment.credit_transfers[].bic | string | "EXMPIE2D" |
| invoice.lines | array<object> · 6 | |
| invoice.lines[].quantity | string | "5" |
| invoice.lines[].unit | string | "DAY" |
| invoice.lines[].price | object | |
| invoice.lines[].price.net | string | "1150.00" |
| invoice.lines[].tax | object | |
| invoice.lines[].tax.category | string | "S" |
| invoice.lines[].tax.rate | string | "23" |
| invoice.lines[].item | object | |
| invoice.lines[].item.name | string | "Product design sprint" |
| invoice.lines[].item.description | string | "Workshops, journey maps and a tested concept" |
| invoice.notes | array<object> · 2 | |
| invoice.notes[].text | string | "Thank you for building the Lanternfield patien…" |
| brand | object | |
| brand.tagline | string | "Product design & engineering" |
| brand.primary | string | "#2b1a3d" |
| brand.accent | string | "#ff8a5c" |
| brand.accent_text | string | "#2b1a3d" |
| website | string | "ferrowquay.example.com" |
| bank_name | string | "Harbourline Bank" |
| payment_link | string | "https://pay.example.com/i/FQ-2026-0419" |
Sample data
Fictional records used for previews. Replace them with your own data when you call the API.
{
"invoice": {
"number": "FQ-2026-0419",
"issue_date": "2026-09-18",
"due_date": "2026-10-18",
"currency": "EUR",
"buyer_reference": "LNF-APP-2026",
"order_reference": "PO-40277",
"payment_terms": "Net 30 days",
"delivery": {
"date": "2026-09-16"
},
"seller": {
"name": "Ferrow & Quay Studio Ltd",
"trading_name": "Ferrow & Quay",
"vat_id": "IE1234567FA",
"legal_registration": { "id": "700419" },
"electronic_address": {
"id": "accounts@ferrowquay.example.com",
"scheme": "EM"
},
"address": {
"line1": "Studio 3, The Ropeworks",
"line2": "14 Marsh Quay Lane",
"city": "Cork",
"postcode": "T12 X7Q4",
"country": "IE"
},
"contact": {
"name": "Studio accounts",
"email": "accounts@ferrowquay.example.com"
}
},
"buyer": {
"name": "Lanternfield Health DAC",
"vat_id": "IE7654321KA",
"electronic_address": {
"id": "payables@lanternfield.example.com",
"scheme": "EM"
},
"address": {
"line1": "Level 4, Weavers Hall",
"line2": "61 Canal Terrace",
"city": "Dublin",
"postcode": "D08 R2Y6",
"country": "IE"
},
"contact": {
"name": "Niamh Corrigan"
}
},
"payment": {
"means_code": "58",
"remittance_information": "FQ-2026-0419",
"credit_transfers": [
{
"account_id": "IE00EXMP99000012345678",
"account_name": "Ferrow & Quay Studio Ltd",
"bic": "EXMPIE2D"
}
]
},
"lines": [
{
"quantity": "5",
"unit": "DAY",
"price": { "net": "1150.00" },
"tax": { "category": "S", "rate": "23" },
"item": {
"name": "Product design sprint",
"description": "Workshops, journey maps and a tested concept"
}
},
{
"quantity": "24",
"unit": "C62",
"price": { "net": "265.00" },
"tax": { "category": "S", "rate": "23" },
"item": {
"name": "App UI screens",
"description": "High-fidelity iOS and Android, light and dark"
}
},
{
"quantity": "1",
"unit": "LS",
"price": { "net": "4800.00" },
"tax": { "category": "S", "rate": "23" },
"item": {
"name": "Design-system components",
"description": "Tokens, 38 components and a shared library"
}
},
{
"quantity": "9",
"unit": "HUR",
"price": { "net": "140.00" },
"tax": { "category": "S", "rate": "23" },
"item": {
"name": "Usability test sessions",
"description": "Six moderated sessions and a findings report"
}
},
{
"quantity": "2.5",
"unit": "DAY",
"price": { "net": "1150.00" },
"tax": { "category": "S", "rate": "23" },
"item": {
"name": "Motion prototypes",
"description": "Onboarding and booking micro-interactions"
}
},
{
"quantity": "14.5",
"unit": "HUR",
"price": { "net": "110.00" },
"tax": { "category": "S", "rate": "23" },
"item": {
"name": "Engineering handoff and QA",
"description": "Front-end pairing and visual QA, release 14"
}
}
],
"notes": [
{ "text": "Thank you for building the Lanternfield patient app with us." },
{ "text": "Questions about this invoice? Write to accounts@ferrowquay.example.com." }
]
},
"brand": {
"tagline": "Product design & engineering",
"primary": "#2b1a3d",
"accent": "#ff8a5c",
"accent_text": "#2b1a3d"
},
"website": "ferrowquay.example.com",
"bank_name": "Harbourline Bank",
"payment_link": "https://pay.example.com/i/FQ-2026-0419"
}Source
The body markup with Jinja, the stylesheet placed in <head> and the header and footer settings.
{#- Bold brand invoice rendered from the EN 16931 invoice model (`invoice`, proto/einvoice/invoice.schema.json).
With `output.einvoice.invoice_path = "invoice"` (this template's default) the renderer embeds the same data as
Factur-X / ZUGFeRD XML; the platform computes the amounts as `render.invoice` for every render of it (with or
without the XML, proto/README.md §20.7), so the page prints exactly the amounts of the XML. `render.einvoice`
only tells whether the XML is embedded. Without either (e.g. an HTML render of the file) the template applies
the same rules itself.
`brand` (tagline and colours, used by head.html and the footer too), `website`, `bank_name` and `payment_link`
are display-only extras outside the invoice model. -#}
{%- set inv = invoice -%}
{%- set cur = inv.currency -%}
{%- set loc = "en-IE" -%}
{%- set b = brand if (brand is defined and brand) else {} -%}
{%- set embedded = render.einvoice if (render is defined and render.einvoice is defined and render.einvoice) else none -%}
{%- set computed = render.invoice if (render is defined and render.invoice is defined and render.invoice) else embedded -%}
{%- set units = {"HUR": ["hour", "hours"], "DAY": ["day", "days"], "WEE": ["week", "weeks"], "MON": ["month", "months"], "C62": ["item", "items"], "H87": ["piece", "pieces"], "LS": ["lump sum", "lump sums"], "E48": ["service", "services"], "D68": ["word", "words"], "ZP": ["page", "pages"], "KGM": ["kg", "kg"], "MTR": ["m", "m"], "LTR": ["l", "l"]} -%}
{%- set countries = {"IE": "Ireland", "GB": "United Kingdom", "DE": "Germany", "FR": "France", "NL": "Netherlands", "BE": "Belgium", "ES": "Spain", "IT": "Italy", "AT": "Austria", "PT": "Portugal", "US": "United States"} -%}
{%- set profiles = {"MINIMUM": "Factur-X MINIMUM", "BASIC_WL": "Factur-X BASIC WL", "BASIC": "Factur-X BASIC", "EN16931": "Factur-X EN 16931", "EXTENDED": "Factur-X EXTENDED", "XRECHNUNG": "XRechnung"} -%}
{%- set exemptions = {"Z": "Zero rated", "E": "Exempt from VAT", "AE": "Reverse charge", "K": "Intra-community supply", "G": "Export outside the EU", "O": "Outside the scope of VAT"} -%}
{#- Line net amounts and VAT groups (EN 16931: line net = quantity × price ÷ base quantity − allowances + charges) -#}
{%- set ns = namespace(rows=[], nets=[], line_total=0.0, allowance_total=0.0, charge_total=0.0) -%}
{%- for line in inv.lines -%}
{%- set base = (line.price.base_quantity | default(1)) | float -%}
{%- set lns = namespace(net=((line.quantity | float) * (line.price.net | float) / base) | round(2)) -%}
{%- for a in line.allowances | default([]) -%}{%- set lns.net = lns.net - (a.amount | float) -%}{%- endfor -%}
{%- for c in line.charges | default([]) -%}{%- set lns.net = lns.net + (c.amount | float) -%}{%- endfor -%}
{%- set net = (computed.lines[loop.index0].net_amount | float) if computed else (lns.net | round(2)) -%}
{%- set rate = (line.tax.rate | default(0)) | float -%}
{%- set ns.nets = ns.nets + [net] -%}
{%- set ns.rows = ns.rows + [{"key": line.tax.category ~ "|" ~ rate, "category": line.tax.category, "rate": rate, "net": net}] -%}
{%- set ns.line_total = ns.line_total + net -%}
{%- endfor -%}
{%- for a in inv.allowances | default([]) -%}
{%- set rate = (a.tax_rate | default(0)) | float -%}
{%- set ns.rows = ns.rows + [{"key": a.tax_category ~ "|" ~ rate, "category": a.tax_category, "rate": rate, "net": 0 - (a.amount | float)}] -%}
{%- set ns.allowance_total = ns.allowance_total + (a.amount | float) -%}
{%- endfor -%}
{%- for c in inv.charges | default([]) -%}
{%- set rate = (c.tax_rate | default(0)) | float -%}
{%- set ns.rows = ns.rows + [{"key": c.tax_category ~ "|" ~ rate, "category": c.tax_category, "rate": rate, "net": c.amount | float}] -%}
{%- set ns.charge_total = ns.charge_total + (c.amount | float) -%}
{%- endfor -%}
{%- if computed -%}
{%- set breakdown = computed.vat_breakdown -%}
{%- set totals = computed.totals -%}
{%- else -%}
{%- set bns = namespace(items=[], tax=0.0) -%}
{%- for group in ns.rows | groupby("key") -%}
{%- set first = group.list | first -%}
{%- set basis = (group.list | sum_by("net")) | round(2) -%}
{%- set tax = (basis * first.rate / 100) | round(2) -%}
{%- set bns.items = bns.items + [{"category": first.category, "rate": first.rate, "basis": basis, "tax": tax}] -%}
{%- set bns.tax = bns.tax + tax -%}
{%- endfor -%}
{%- set tax_basis = (ns.line_total - ns.allowance_total + ns.charge_total) | round(2) -%}
{%- set grand = (tax_basis + bns.tax) | round(2) -%}
{%- set prepaid = (inv.prepaid_amount | default(0)) | float -%}
{%- set rounding = (inv.rounding_amount | default(0)) | float -%}
{%- set breakdown = bns.items -%}
{%- set totals = {"line_total": ns.line_total | round(2), "allowance_total": ns.allowance_total | round(2), "charge_total": ns.charge_total | round(2), "tax_basis_total": tax_basis, "tax_total": bns.tax | round(2), "grand_total": grand, "prepaid": prepaid, "rounding": rounding, "due_payable": (grand - prepaid + rounding) | round(2)} -%}
{%- endif -%}
{%- set several_rates = breakdown | length > 1 -%}
{%- set adjusted = (totals.prepaid | float) != 0 or (totals.rounding | float) != 0 -%}
{%- set is_credit = inv.type_code in ["381", "261"] -%}
{%- macro money(value) -%}{{ value | format_currency(cur, loc) }}{%- endmacro -%}
{%- macro day(value) -%}{{ value | format_date("long", loc) }}{%- endmacro -%}
{%- macro rate_label(vb) -%}
{%- if vb.category in exemptions -%}{{ exemptions[vb.category] }}{%- else -%}VAT {{ (vb.rate | float) | format_number(loc) }}%{%- endif -%}
{%- endmacro -%}
{%- macro quantity(line) -%}
{%- set names = units[line.unit or "C62"] -%}
{{ line.quantity | format_number(loc) }} <span class="unit">{% if names %}{{ names[0] if (line.quantity | float) == 1 else names[1] }}{% else %}{{ line.unit }}{% endif %}</span>
{%- endmacro -%}
{%- macro address(party) -%}
{% if party.address.line1 %}<p>{{ party.address.line1 }}</p>{% endif %}
{% if party.address.line2 %}<p>{{ party.address.line2 }}</p>{% endif %}
{% if party.address.line3 %}<p>{{ party.address.line3 }}</p>{% endif %}
<p>{% if party.address.country in ["GB", "IE", "US"] %}{{ party.address.city }} {{ party.address.postcode }}{% else %}{{ party.address.postcode }} {{ party.address.city }}{% endif %}, {{ countries[party.address.country] or party.address.country }}</p>
{%- endmacro -%}
{%- macro grouped(value) -%}
{%- for i in range(0, value | length, 4) -%}{{ value[i:i + 4] }}{{ " " if not loop.last }}{%- endfor -%}
{%- endmacro -%}
<section class="hero">
{#- Motif: stepped tones and rings around the amount due, with an accent dot rising along its orbit
(1 unit = 1 mm, anchored to the hero's bottom-left corner) -#}
<svg class="hero-motif" viewBox="0 0 182 70" aria-hidden="true">
<defs>
<linearGradient id="hero-trail" gradientUnits="userSpaceOnUse" x1="91.3" y1="66.6" x2="102.4" y2="31.5">
<stop offset="0" class="trail-start"/>
<stop offset="1" class="trail-end"/>
</linearGradient>
</defs>
{%- for r in [118, 96, 74, 52] %}
<circle class="tone" cx="176" cy="74" r="{{ r }}"/>
{%- endfor %}
{%- for r in [129, 107, 85, 63, 41] %}
<circle class="ring" cx="176" cy="74" r="{{ r }}"/>
{%- endfor %}
<path class="trail" d="M 91.32 66.59 A 85 85 0 0 1 102.39 31.5"/>
<circle class="orbit" cx="102.39" cy="31.5" r="1.6"/>
</svg>
<div class="hero-top">
<div class="brand">
<svg class="mark" viewBox="0 0 48 48" aria-hidden="true">
<rect class="mark-bg" width="48" height="48" rx="13"/>
<path class="mark-ink" d="M13 35 A22 22 0 0 1 35 13"/>
<circle class="mark-dot" cx="32" cy="32" r="4.6"/>
</svg>
<div>
<div class="brand-name">{{ inv.seller.trading_name or inv.seller.name }}</div>
{% if b.tagline %}<div class="brand-tag">{{ b.tagline }}</div>{% endif %}
</div>
</div>
<div class="hero-contact">
{% if website is defined and website %}{{ website }}<br>{% endif %}
{% if inv.seller.contact and inv.seller.contact.email %}{{ inv.seller.contact.email }}{% endif %}
</div>
</div>
<div class="hero-main">
<div>
<h1>{{ "Credit note" if is_credit else "Invoice" }}</h1>
<div class="doc-number"><span>No.</span>{{ inv.number }}</div>
</div>
<div class="due">
<div class="kicker">{{ "Amount credited" if is_credit else "Amount due" }}</div>
<div class="due-amount">{{ money(totals.due_payable) }}</div>
{% if inv.due_date %}<div class="due-date"><span>Due</span> {{ day(inv.due_date) }}</div>
{% elif inv.payment_terms %}<div class="due-date">{{ inv.payment_terms }}</div>{% endif %}
</div>
</div>
</section>
<section class="strip">
<div>
<p class="kicker">Billed to</p>
<p class="party-name">{{ inv.buyer.name }}</p>
{% if inv.buyer.contact and inv.buyer.contact.name %}<p>Attn. {{ inv.buyer.contact.name }}</p>{% endif %}
<div class="muted">{{ address(inv.buyer) }}</div>
{% if inv.buyer.vat_id %}<p class="muted">VAT {{ inv.buyer.vat_id }}</p>{% endif %}
</div>
<div>
<p class="kicker">From</p>
<p class="party-name">{{ inv.seller.name }}</p>
<div class="muted">{{ address(inv.seller) }}</div>
{% if inv.seller.vat_id %}<p class="muted">VAT {{ inv.seller.vat_id }}</p>{% endif %}
{% if inv.seller.legal_registration %}<p class="muted">Company no. {{ inv.seller.legal_registration.id }}</p>{% endif %}
</div>
<div>
<p class="kicker">Details</p>
<table class="facts">
<tr><th scope="row">Issue date</th><td>{{ day(inv.issue_date) }}</td></tr>
{% if inv.delivery and inv.delivery.date %}<tr><th scope="row">Supply date</th><td>{{ day(inv.delivery.date) }}</td></tr>{% endif %}
{% if inv.invoice_period %}<tr><th scope="row">Period</th><td>{{ inv.invoice_period.start | format_date("medium", loc) }} – {{ inv.invoice_period.end | format_date("medium", loc) }}</td></tr>{% endif %}
{% if inv.payment_terms %}<tr><th scope="row">Terms</th><td>{{ inv.payment_terms }}</td></tr>{% endif %}
{% if inv.order_reference %}<tr><th scope="row">PO number</th><td>{{ inv.order_reference }}</td></tr>{% endif %}
{% if inv.buyer_reference %}<tr><th scope="row">Your reference</th><td>{{ inv.buyer_reference }}</td></tr>{% endif %}
{% if embedded %}<tr><th scope="row">E-invoice</th><td>{{ profiles[embedded.profile] or embedded.profile }}</td></tr>{% endif %}
</table>
</div>
</section>
<table class="items">
<thead>
<tr>
<th class="index" scope="col">No.</th>
<th scope="col">Item</th>
<th class="num col-qty" scope="col">Qty</th>
<th class="num col-price" scope="col">Unit price</th>
{% if several_rates %}<th class="num col-vat" scope="col">VAT</th>{% endif %}
<th class="num col-amount" scope="col">Amount</th>
</tr>
</thead>
<tbody>
{% for line in inv.lines %}
<tr>
<td class="index">{% if line.id %}{{ line.id }}{% else %}{{ "0" if loop.index < 10 }}{{ loop.index }}{% endif %}</td>
<td>
<span class="item-name">{{ line.item.name }}</span>
{% if line.item.description %}<span class="details">{{ line.item.description }}</span>{% endif %}
{% if line.note %}<span class="details">{{ line.note }}</span>{% endif %}
{% for a in line.allowances | default([]) %}<span class="details">Less {{ a.reason or "discount" }}: {{ money(a.amount | float) }}</span>{% endfor %}
{% for c in line.charges | default([]) %}<span class="details">Plus {{ c.reason or "charge" }}: {{ money(c.amount | float) }}</span>{% endfor %}
</td>
<td class="num">{{ quantity(line) }}</td>
<td class="num">{{ money(line.price.net) }}</td>
{% if several_rates %}<td class="num">{% if line.tax.category in exemptions %}{{ line.tax.category }}{% else %}{{ (line.tax.rate | float) | format_number(loc) }}%{% endif %}</td>{% endif %}
<td class="num amount">{{ money(ns.nets[loop.index0]) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<section class="summary">
<div>
{% if inv.payment %}
<div class="payment">
<div class="pay-details">
<h2>How to pay</h2>
<table class="pay-table">
{% for account in inv.payment.credit_transfers | default([]) %}
<tr><th scope="row">Account name</th><td>{{ account.account_name or inv.seller.name }}</td></tr>
<tr><th scope="row">{{ "IBAN" if (account.account_id | length) >= 15 else "Account" }}</th><td>{{ grouped(account.account_id) }}</td></tr>
{% if account.bic %}<tr><th scope="row">BIC</th><td>{{ account.bic }}</td></tr>{% endif %}
{% if bank_name is defined and bank_name and loop.first %}<tr><th scope="row">Bank</th><td>{{ bank_name }}</td></tr>{% endif %}
{% endfor %}
<tr><th scope="row">Reference</th><td><strong>{{ inv.payment.remittance_information or inv.number }}</strong></td></tr>
</table>
</div>
{% if payment_link is defined and payment_link %}
<div class="qr">
<div class="qr-tile">{{ payment_link | qrcode(size=240, margin=0, ecc="M", color=(b.primary or "#2b1a3d"), alt="QR code linking to the online payment page") }}</div>
<p class="qr-caption">Scan to pay</p>
</div>
{% endif %}
</div>
{% endif %}
</div>
<div class="totals-block">
<table class="totals">
<tr><th scope="row">Subtotal</th><td class="num">{{ money(totals.line_total) }}</td></tr>
{% if (totals.allowance_total | float) != 0 %}<tr><th scope="row">Discounts</th><td class="num">−{{ money(totals.allowance_total) }}</td></tr>{% endif %}
{% if (totals.charge_total | float) != 0 %}<tr><th scope="row">Charges</th><td class="num">{{ money(totals.charge_total) }}</td></tr>{% endif %}
{% if (totals.allowance_total | float) != 0 or (totals.charge_total | float) != 0 %}<tr><th scope="row">Net total</th><td class="num">{{ money(totals.tax_basis_total) }}</td></tr>{% endif %}
{% for vb in breakdown %}
<tr><th scope="row">{{ rate_label(vb) }}{% if several_rates %} on {{ money(vb.basis) }}{% endif %}</th><td class="num">{{ money(vb.tax) }}</td></tr>
{% endfor %}
{% if adjusted %}
<tr class="strong"><th scope="row">Invoice total</th><td class="num">{{ money(totals.grand_total) }}</td></tr>
{% if (totals.prepaid | float) != 0 %}<tr><th scope="row">Paid</th><td class="num">−{{ money(totals.prepaid) }}</td></tr>{% endif %}
{% if (totals.rounding | float) != 0 %}<tr><th scope="row">Rounding</th><td class="num">{{ money(totals.rounding) }}</td></tr>{% endif %}
{% endif %}
</table>
<div class="total-bar">
<div class="label">{{ "Total credit" if is_credit else ("Amount due" if adjusted else "Total due") }}<small>{{ cur }}, incl. VAT</small></div>
<div class="value">{{ money(totals.due_payable) }}</div>
</div>
</div>
</section>
{%- set notes = inv.notes | default([]) -%}
{%- set ens = namespace(items=[]) -%}
{%- for vb in breakdown -%}
{%- if vb.category in exemptions and inv.vat_exemptions and inv.vat_exemptions[vb.category] and inv.vat_exemptions[vb.category].reason -%}
{%- set ens.items = ens.items + [exemptions[vb.category] ~ ": " ~ inv.vat_exemptions[vb.category].reason] -%}
{%- endif -%}
{%- endfor %}
{% if notes or ens.items %}
<section class="closing">
{% if notes %}<p class="thanks">{{ (notes | first).text }}</p>{% endif %}
<div class="notes">
{% for note in notes[1:] %}<p>{{ note.text }}</p>{% endfor %}
{% for text in ens.items %}<p>{{ text }}</p>{% endfor %}
</div>
</section>
{% endif %}{#- Brand colours come from `brand` in the data (head.html is rendered with the same Jinja context as the body).
primary: the hero and headings (a deep colour that carries white text); accent: the amount due, the table
header and the total bar; accent_text: text on the accent (defaults to primary). Every tint below is mixed
from these two, so changing them re-brands the whole invoice. -#}
{%- set b = brand if (brand is defined and brand) else {} -%}
<title>{{ "Credit note" if invoice.type_code in ["381", "261"] else "Invoice" }} {{ invoice.number }} – {{ invoice.seller.trading_name or invoice.seller.name }}</title>
<style>
:root {
--primary: {{ b.primary or "#2b1a3d" }};
--accent: {{ b.accent or "#ff8a5c" }};
--on-accent: {{ b.accent_text or b.primary or "#2b1a3d" }};
--ink: #221a2b;
--muted: #6d6477;
--rule: color-mix(in srgb, var(--primary) 13%, white);
--tint: color-mix(in srgb, var(--primary) 9%, white);
--hero-muted: color-mix(in srgb, var(--primary) 38%, white);
--display: "Poppins", "Montserrat", "Liberation Sans", Arial, sans-serif;
}
* { box-sizing: border-box; }
html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
body {
margin: 0;
color: var(--ink);
font-family: "Inter", "Liberation Sans", Arial, sans-serif;
font-size: 9pt;
line-height: 1.45;
}
p { margin: 0; }
.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.muted { color: var(--muted); }
.kicker {
font-size: 7pt; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--primary);
}
/* Hero */
.hero {
position: relative; overflow: hidden; border-radius: 4mm;
background: var(--primary); color: #ffffff;
padding: 7.5mm 9mm 8.5mm;
break-inside: avoid; page-break-inside: avoid;
}
.hero-motif { position: absolute; left: 0; bottom: 0; width: 182mm; height: 70mm; overflow: visible; }
.hero-motif .tone { fill: #ffffff; fill-opacity: 0.035; }
.hero-motif .ring { fill: none; stroke: #ffffff; stroke-opacity: 0.1; stroke-width: 0.3; }
.hero-motif .orbit { fill: var(--accent); }
.hero-motif .trail { fill: none; stroke: url(#hero-trail); stroke-width: 0.55; stroke-linecap: round; }
.hero-motif .trail-start { stop-color: var(--accent); stop-opacity: 0; }
.hero-motif .trail-end { stop-color: var(--accent); stop-opacity: 0.9; }
.hero-top, .hero-main { position: relative; display: flex; justify-content: space-between; }
.hero-top { align-items: center; }
.hero-main { align-items: flex-end; margin-top: 8mm; }
.brand { display: flex; align-items: center; gap: 3.2mm; }
.mark { width: 11mm; height: 11mm; flex: none; }
.mark .mark-bg { fill: var(--accent); }
.mark .mark-ink { fill: none; stroke: var(--primary); stroke-width: 5; stroke-linecap: round; }
.mark .mark-dot { fill: var(--primary); }
.brand-name { font-family: var(--display); font-size: 13pt; font-weight: 600; letter-spacing: -0.01em; line-height: 1.15; }
.brand-tag { font-size: 8pt; color: var(--hero-muted); }
.hero-contact { text-align: right; font-size: 8pt; line-height: 1.5; color: var(--hero-muted); }
.hero h1 {
margin: 0; font-family: var(--display); font-size: 42pt; font-weight: 600; line-height: 0.95; letter-spacing: -0.03em;
}
.doc-number {
display: inline-block; margin-top: 3.5mm; padding: 1mm 3.2mm; border-radius: 5mm;
border: 0.3mm solid color-mix(in srgb, var(--accent) 70%, transparent);
font-size: 9pt; font-weight: 600; letter-spacing: 0.02em; font-variant-numeric: tabular-nums;
}
.doc-number span { color: var(--hero-muted); font-weight: 500; margin-right: 1.2mm; }
.due { text-align: right; }
.due .kicker { color: var(--hero-muted); }
.due-amount {
margin: 1mm 0 1.2mm; font-family: var(--display); font-size: 31pt; font-weight: 600; line-height: 1;
letter-spacing: -0.02em; color: var(--accent); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.due-date { font-size: 9.5pt; font-weight: 500; }
.due-date span { color: var(--hero-muted); font-weight: 400; }
/* Parties and details */
.strip {
display: grid; grid-template-columns: 1fr 1fr 1.15fr; margin: 6.5mm 0 6.5mm; line-height: 1.5;
overflow-wrap: anywhere;
}
.strip > div { padding: 0 6mm; border-left: 0.3mm solid var(--rule); }
.strip > div:first-child { padding-left: 0; border-left: none; }
.strip > div:last-child { padding-right: 0; }
.strip .kicker { display: flex; align-items: center; gap: 1.6mm; margin-bottom: 2mm; }
.strip .kicker::before {
content: ""; width: 1.8mm; height: 1.8mm; border-radius: 50%; background: var(--accent); flex: none;
}
.party-name { margin-bottom: 0.4mm; font-weight: 700; font-size: 9.5pt; color: var(--ink); }
.facts { width: 100%; border-collapse: collapse; }
.facts td, .facts th { padding: 0; vertical-align: top; }
.facts th { color: var(--muted); padding-right: 3mm; white-space: nowrap; font-weight: inherit; text-align: inherit; }
.facts td:last-child { text-align: right; font-weight: 500; }
/* Items */
table.items { width: 100%; border-collapse: separate; border-spacing: 0; }
table.items thead { display: table-header-group; }
table.items th {
color: var(--on-accent);
font-size: 7pt; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase;
text-align: left; padding: 2.3mm 3mm;
}
/* The accent bar is painted once, as one rounded shape behind the whole header row: per-cell backgrounds
rasterize with seams and steps where rounded and square cells meet. It repeats with thead on every page. */
table.items thead tr { position: relative; z-index: 0; }
table.items th:first-child::before {
content: ""; position: absolute; inset: 0; z-index: -1; border-radius: 2mm; background: var(--accent);
}
table.items th.num { text-align: right; }
table.items td { padding: 2.6mm 3mm 2.7mm; border-bottom: 0.25mm solid var(--rule); vertical-align: top; line-height: 1.35; }
table.items tr { break-inside: avoid; page-break-inside: avoid; }
table.items .index { width: 11mm; }
table.items td.index { font-family: var(--display); font-weight: 600; font-size: 9pt; color: var(--primary); font-variant-numeric: tabular-nums; }
table.items .item-name { font-weight: 600; font-size: 9.5pt; color: var(--ink); }
table.items .details { display: block; font-size: 8pt; color: var(--muted); margin-top: 0.3mm; }
table.items .col-qty { width: 23mm; }
table.items .col-price { width: 25mm; }
table.items .col-vat { width: 14mm; }
table.items .col-amount { width: 27mm; }
table.items td.amount { font-weight: 600; color: var(--ink); }
table.items .unit { color: var(--muted); }
/* Summary: payment on the left, totals on the right */
.summary {
display: grid; grid-template-columns: 1fr 74mm; gap: 8mm; align-items: stretch; margin-top: 6mm;
break-inside: avoid; page-break-inside: avoid;
}
.totals-block { display: flex; flex-direction: column; justify-content: space-between; }
.totals { width: 100%; border-collapse: collapse; }
.totals td, .totals th { padding: 1.3mm 3mm; }
.totals th { color: var(--muted); font-weight: inherit; text-align: inherit; }
.totals .strong td, .totals .strong th { color: var(--ink); font-weight: 700; border-top: 0.3mm solid var(--rule); padding-top: 2.2mm; }
.total-bar {
display: flex; justify-content: space-between; align-items: center; gap: 4mm;
margin-top: 2.2mm; padding: 3.2mm 4mm; border-radius: 2.5mm;
background: var(--accent); color: var(--on-accent);
}
.total-bar .label { font-size: 7.5pt; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; }
.total-bar .label small { display: block; font-size: 7pt; font-weight: 500; letter-spacing: 0.02em; text-transform: none; opacity: 0.8; }
.total-bar .value { font-family: var(--display); font-size: 17pt; font-weight: 600; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Payment */
.payment {
display: flex; justify-content: space-between; align-items: center; gap: 5mm;
padding: 4.5mm 4.5mm 4.5mm 6mm; border-radius: 3mm; background: var(--tint);
}
.payment h2 { margin: 0 0 1.6mm; font-family: var(--display); font-size: 11pt; font-weight: 600; color: var(--primary); letter-spacing: -0.01em; }
.pay-table { border-collapse: collapse; font-size: 8.5pt; }
.pay-table td, .pay-table th { padding: 0.35mm 0; vertical-align: top; }
.pay-table th { color: var(--muted); padding-right: 4mm; white-space: nowrap; font-weight: inherit; text-align: inherit; }
.pay-table td:last-child { font-weight: 500; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pay-table strong { font-weight: 700; color: var(--primary); }
.qr { flex: none; text-align: center; }
.qr-tile { display: inline-block; padding: 1.8mm; border-radius: 2mm; background: #ffffff; }
.qr img { display: block; width: 20mm; height: 20mm; }
.qr-caption { margin-top: 1.2mm; font-size: 7pt; font-weight: 600; letter-spacing: 0.02em; color: var(--primary); }
/* Closing */
.closing {
margin-top: 5.5mm; padding-top: 3.5mm; border-top: 0.3mm solid var(--rule);
break-inside: avoid; page-break-inside: avoid;
}
.thanks { font-family: var(--display); font-size: 11.5pt; font-weight: 600; line-height: 1.3; color: var(--primary); letter-spacing: -0.005em; }
.notes { margin-top: 1mm; font-size: 8pt; color: var(--muted); }
.notes p + p { margin-top: 0.8mm; }
</style>Render it with the API
After adding the template to your workspace, render it by ID and pass your data. Arrays are shortened here.
curl -X POST https://api-eu.dynamicdocumentapi.com/v1/renders \
-H "Authorization: Bearer $DYNAMIC_DOCUMENT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"input": {
"type": "template",
"template_id": "tpl_01J9ZK3M7Q8V5W2X4Y6Z8A0B1C"
},
"data": {
"invoice": {
"number": "FQ-2026-0419",
"issue_date": "2026-09-18",
"due_date": "2026-10-18",
"currency": "EUR",
"buyer_reference": "LNF-APP-2026",
"order_reference": "PO-40277",
"payment_terms": "Net 30 days",
"delivery": {
"date": "2026-09-16"
},
"seller": {
"name": "Ferrow & Quay Studio Ltd",
"trading_name": "Ferrow & Quay",
"vat_id": "IE1234567FA",
"legal_registration": {
"id": "700419"
},
"electronic_address": {
"id": "accounts@ferrowquay.example.com",
"scheme": "EM"
},
"address": {
"line1": "Studio 3, The Ropeworks",
"line2": "14 Marsh Quay Lane",
"city": "Cork",
"postcode": "T12 X7Q4",
"country": "IE"
},
"contact": {
"name": "Studio accounts",
"email": "accounts@ferrowquay.example.com"
}
},
"buyer": {
"name": "Lanternfield Health DAC",
"vat_id": "IE7654321KA",
"electronic_address": {
"id": "payables@lanternfield.example.com",
"scheme": "EM"
},
"address": {
"line1": "Level 4, Weavers Hall",
"line2": "61 Canal Terrace",
"city": "Dublin",
"postcode": "D08 R2Y6",
"country": "IE"
},
"contact": {
"name": "Niamh Corrigan"
}
},
"payment": {
"means_code": "58",
"remittance_information": "FQ-2026-0419",
"credit_transfers": [
{
"account_id": "IE00EXMP99000012345678",
"account_name": "Ferrow & Quay Studio Ltd",
"bic": "EXMPIE2D"
}
]
},
"lines": [
{
"quantity": "5",
"unit": "DAY",
"price": {
"net": "1150.00"
},
"tax": {
"category": "S",
"rate": "23"
},
"item": {
"name": "Product design sprint",
"description": "Workshops, journey maps and a tested concept"
}
},
{
"quantity": "24",
"unit": "C62",
"price": {
"net": "265.00"
},
"tax": {
"category": "S",
"rate": "23"
},
"item": {
"name": "App UI screens",
"description": "High-fidelity iOS and Android, light and dark"
}
}
],
"notes": [
{
"text": "Thank you for building the Lanternfield patient app with us."
},
{
"text": "Questions about this invoice? Write to accounts@ferrowquay.example.com."
}
]
},
"brand": {
"tagline": "Product design & engineering",
"primary": "#2b1a3d",
"accent": "#ff8a5c",
"accent_text": "#2b1a3d"
},
"website": "ferrowquay.example.com",
"bank_name": "Harbourline Bank",
"payment_link": "https://pay.example.com/i/FQ-2026-0419"
},
"output": {
"format": "pdf",
"filename": "invoice-bold-brand.pdf"
}
}'const response = await fetch("https://api-eu.dynamicdocumentapi.com/v1/renders", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DYNAMIC_DOCUMENT_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"input": {
"type": "template",
"template_id": "tpl_01J9ZK3M7Q8V5W2X4Y6Z8A0B1C"
},
"data": {
"invoice": {
"number": "FQ-2026-0419",
"issue_date": "2026-09-18",
"due_date": "2026-10-18",
"currency": "EUR",
"buyer_reference": "LNF-APP-2026",
"order_reference": "PO-40277",
"payment_terms": "Net 30 days",
"delivery": {
"date": "2026-09-16"
},
"seller": {
"name": "Ferrow & Quay Studio Ltd",
"trading_name": "Ferrow & Quay",
"vat_id": "IE1234567FA",
"legal_registration": {
"id": "700419"
},
"electronic_address": {
"id": "accounts@ferrowquay.example.com",
"scheme": "EM"
},
"address": {
"line1": "Studio 3, The Ropeworks",
"line2": "14 Marsh Quay Lane",
"city": "Cork",
"postcode": "T12 X7Q4",
"country": "IE"
},
"contact": {
"name": "Studio accounts",
"email": "accounts@ferrowquay.example.com"
}
},
"buyer": {
"name": "Lanternfield Health DAC",
"vat_id": "IE7654321KA",
"electronic_address": {
"id": "payables@lanternfield.example.com",
"scheme": "EM"
},
"address": {
"line1": "Level 4, Weavers Hall",
"line2": "61 Canal Terrace",
"city": "Dublin",
"postcode": "D08 R2Y6",
"country": "IE"
},
"contact": {
"name": "Niamh Corrigan"
}
},
"payment": {
"means_code": "58",
"remittance_information": "FQ-2026-0419",
"credit_transfers": [
{
"account_id": "IE00EXMP99000012345678",
"account_name": "Ferrow & Quay Studio Ltd",
"bic": "EXMPIE2D"
}
]
},
"lines": [
{
"quantity": "5",
"unit": "DAY",
"price": {
"net": "1150.00"
},
"tax": {
"category": "S",
"rate": "23"
},
"item": {
"name": "Product design sprint",
"description": "Workshops, journey maps and a tested concept"
}
},
{
"quantity": "24",
"unit": "C62",
"price": {
"net": "265.00"
},
"tax": {
"category": "S",
"rate": "23"
},
"item": {
"name": "App UI screens",
"description": "High-fidelity iOS and Android, light and dark"
}
}
],
"notes": [
{
"text": "Thank you for building the Lanternfield patient app with us."
},
{
"text": "Questions about this invoice? Write to accounts@ferrowquay.example.com."
}
]
},
"brand": {
"tagline": "Product design & engineering",
"primary": "#2b1a3d",
"accent": "#ff8a5c",
"accent_text": "#2b1a3d"
},
"website": "ferrowquay.example.com",
"bank_name": "Harbourline Bank",
"payment_link": "https://pay.example.com/i/FQ-2026-0419"
},
"output": {
"format": "pdf",
"filename": "invoice-bold-brand.pdf"
}
}),
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const result = await response.json();
console.log(result);import os
import uuid
import requests
response = requests.post(
"https://api-eu.dynamicdocumentapi.com/v1/renders",
headers={
"Authorization": f"Bearer {os.environ['DYNAMIC_DOCUMENT_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"input": {
"type": "template",
"template_id": "tpl_01J9ZK3M7Q8V5W2X4Y6Z8A0B1C",
},
"data": {
"invoice": {
"number": "FQ-2026-0419",
"issue_date": "2026-09-18",
"due_date": "2026-10-18",
"currency": "EUR",
"buyer_reference": "LNF-APP-2026",
"order_reference": "PO-40277",
"payment_terms": "Net 30 days",
"delivery": {
"date": "2026-09-16",
},
"seller": {
"name": "Ferrow & Quay Studio Ltd",
"trading_name": "Ferrow & Quay",
"vat_id": "IE1234567FA",
"legal_registration": {
"id": "700419",
},
"electronic_address": {
"id": "accounts@ferrowquay.example.com",
"scheme": "EM",
},
"address": {
"line1": "Studio 3, The Ropeworks",
"line2": "14 Marsh Quay Lane",
"city": "Cork",
"postcode": "T12 X7Q4",
"country": "IE",
},
"contact": {
"name": "Studio accounts",
"email": "accounts@ferrowquay.example.com",
},
},
"buyer": {
"name": "Lanternfield Health DAC",
"vat_id": "IE7654321KA",
"electronic_address": {
"id": "payables@lanternfield.example.com",
"scheme": "EM",
},
"address": {
"line1": "Level 4, Weavers Hall",
"line2": "61 Canal Terrace",
"city": "Dublin",
"postcode": "D08 R2Y6",
"country": "IE",
},
"contact": {
"name": "Niamh Corrigan",
},
},
"payment": {
"means_code": "58",
"remittance_information": "FQ-2026-0419",
"credit_transfers": [
{
"account_id": "IE00EXMP99000012345678",
"account_name": "Ferrow & Quay Studio Ltd",
"bic": "EXMPIE2D",
},
],
},
"lines": [
{
"quantity": "5",
"unit": "DAY",
"price": {
"net": "1150.00",
},
"tax": {
"category": "S",
"rate": "23",
},
"item": {
"name": "Product design sprint",
"description": "Workshops, journey maps and a tested concept",
},
},
{
"quantity": "24",
"unit": "C62",
"price": {
"net": "265.00",
},
"tax": {
"category": "S",
"rate": "23",
},
"item": {
"name": "App UI screens",
"description": "High-fidelity iOS and Android, light and dark",
},
},
],
"notes": [
{
"text": "Thank you for building the Lanternfield patient app with us.",
},
{
"text": "Questions about this invoice? Write to accounts@ferrowquay.example.com.",
},
],
},
"brand": {
"tagline": "Product design & engineering",
"primary": "#2b1a3d",
"accent": "#ff8a5c",
"accent_text": "#2b1a3d",
},
"website": "ferrowquay.example.com",
"bank_name": "Harbourline Bank",
"payment_link": "https://pay.example.com/i/FQ-2026-0419",
},
"output": {
"format": "pdf",
"filename": "invoice-bold-brand.pdf",
},
},
timeout=90,
)
response.raise_for_status()
print(response.json())More invoices templates
- 210 × 297 mm
E-Rechnung (ZUGFeRD / XRechnung)
A4A German A4 invoice rendered straight from the EN 16931 invoice model, so the same data produces the visible invoice and the embedded Factur-X / ZUGFeRD or XRechnung XML (output.einvoice.invoice_path = "invoice"). It prints the engine's computed amounts, lists VAT per rate, the Leitweg-ID and the bank details.
- 210 × 297 mm
EU reverse-charge invoice
A4An A4 invoice for cross-border B2B services within the EU, rendered from the EN 16931 invoice model: both VAT IDs, the date of supply, VAT category AE (reverse charge) with its exemption reason and notice, the total in words, bank transfer details and the same data embedded as ZUGFeRD / Factur-X EN 16931 XML.
- 210 × 297 mm
Freelancer invoice
A4A friendly A4 invoice for freelancers, easy to edit visually: a wordmark heading, bill-to and project details, service lines, VAT totals, bank details with a pay-by-link QR code, a personal thank-you note and page numbers. Embeds ZUGFeRD / Factur-X EN 16931 XML.