210 × 297 mm
Invoices
EU reverse-charge invoice
An 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.
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
- 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: 4 top-level fields, 68 paths in total. Send the same shape as data.
| Path | Type | Example |
|---|---|---|
| invoice | object | |
| invoice.number | string | "SLS-2026-0412" |
| invoice.issue_date | date | "2026-09-04" |
| invoice.due_date | date | "2026-10-04" |
| invoice.currency | string | "EUR" |
| invoice.buyer_reference | string | "PO 4500-2291" |
| invoice.payment_terms | string | "Payment within 30 days of the invoice date by …" |
| invoice.delivery | object | |
| invoice.delivery.date | date | "2026-08-31" |
| invoice.seller | object | |
| invoice.seller.name | string | "Saltmarsh Language Services Ltd" |
| invoice.seller.trading_name | string | "Saltmarsh Language Services" |
| invoice.seller.vat_id | string | "IE1234567T" |
| invoice.seller.legal_registration | object | |
| invoice.seller.legal_registration.id | string | "123456" |
| invoice.seller.legal_info | string | "Registered in Ireland, company no. 123456" |
| invoice.seller.electronic_address | object | |
| invoice.seller.electronic_address.id | string | "accounts@saltmarsh.example.com" |
| invoice.seller.electronic_address.scheme | string | "EM" |
| invoice.seller.address | object | |
| invoice.seller.address.line1 | string | "Unit 7, Claddagh Quay Business Park" |
| invoice.seller.address.city | string | "Galway" |
| invoice.seller.address.postcode | string | "H91 X2Y3" |
| invoice.seller.address.country | string | "IE" |
| invoice.seller.contact | object | |
| invoice.seller.contact.name | string | "Accounts" |
| invoice.seller.contact.email | string | "accounts@saltmarsh.example.com" |
| invoice.buyer | object | |
| invoice.buyer.name | string | "Lindgrün Gartenmöbel GmbH" |
| invoice.buyer.vat_id | string | "DE123456789" |
| invoice.buyer.electronic_address | object | |
| invoice.buyer.electronic_address.id | string | "rechnungen@lindgruen.example.com" |
| invoice.buyer.electronic_address.scheme | string | "EM" |
| invoice.buyer.address | object | |
| invoice.buyer.address.line1 | string | "Hafenstraße 12" |
| invoice.buyer.address.city | string | "Hamburg" |
| invoice.buyer.address.postcode | string | "20457" |
| invoice.buyer.address.country | string | "DE" |
| invoice.buyer.contact | object | |
| invoice.buyer.contact.name | string | "Katrin Vogelsang, Marketing & E-Commerce" |
| invoice.payment | object | |
| invoice.payment.means_code | string | "58" |
| invoice.payment.remittance_information | string | "SLS-2026-0412" |
| invoice.payment.credit_transfers | array<object> · 1 | |
| invoice.payment.credit_transfers[].account_id | string | "IE74EXMP99000012345678" |
| invoice.payment.credit_transfers[].account_name | string | "Saltmarsh Language Services Ltd" |
| invoice.payment.credit_transfers[].bic | string | "EXMPIE2D" |
| invoice.vat_exemptions | object | |
| invoice.vat_exemptions.AE | object | |
| invoice.vat_exemptions.AE.reason | string | "Reverse charge (Article 196, Council Directive…" |
| invoice.vat_exemptions.AE.reason_code | string | "VATEX-EU-AE" |
| invoice.lines | array<object> · 6 | |
| invoice.lines[].quantity | string | "18400" |
| invoice.lines[].unit | string | "D68" |
| invoice.lines[].price | object | |
| invoice.lines[].price.net | string | "0.12" |
| invoice.lines[].tax | object | |
| invoice.lines[].tax.category | string | "AE" |
| invoice.lines[].tax.rate | string | "0" |
| invoice.lines[].item | object | |
| invoice.lines[].item.name | string | "Product catalogue translation, German to Engli…" |
| invoice.lines[].item.description | string | "Autumn/winter range, 412 product pages" |
| invoice.notes | array<object> · 2 | |
| invoice.notes[].text | string | "Supply of services to a business customer esta…" |
| invoice.notes[].subject_code? | string | "TXD" |
| services | string | "Translation · Localisation · Typesetting" |
| website | string | "saltmarsh.example.com" |
| bank_name | string | "Corrib Mutual Bank" |
Sample data
Fictional records used for previews. Replace them with your own data when you call the API.
{
"invoice": {
"number": "SLS-2026-0412",
"issue_date": "2026-09-04",
"due_date": "2026-10-04",
"currency": "EUR",
"buyer_reference": "PO 4500-2291",
"payment_terms": "Payment within 30 days of the invoice date by SEPA credit transfer. Bank charges on international transfers are paid by the sender.",
"delivery": {
"date": "2026-08-31"
},
"seller": {
"name": "Saltmarsh Language Services Ltd",
"trading_name": "Saltmarsh Language Services",
"vat_id": "IE1234567T",
"legal_registration": {
"id": "123456"
},
"legal_info": "Registered in Ireland, company no. 123456",
"electronic_address": {
"id": "accounts@saltmarsh.example.com",
"scheme": "EM"
},
"address": {
"line1": "Unit 7, Claddagh Quay Business Park",
"city": "Galway",
"postcode": "H91 X2Y3",
"country": "IE"
},
"contact": {
"name": "Accounts",
"email": "accounts@saltmarsh.example.com"
}
},
"buyer": {
"name": "Lindgrün Gartenmöbel GmbH",
"vat_id": "DE123456789",
"electronic_address": {
"id": "rechnungen@lindgruen.example.com",
"scheme": "EM"
},
"address": {
"line1": "Hafenstraße 12",
"city": "Hamburg",
"postcode": "20457",
"country": "DE"
},
"contact": {
"name": "Katrin Vogelsang, Marketing & E-Commerce"
}
},
"payment": {
"means_code": "58",
"remittance_information": "SLS-2026-0412",
"credit_transfers": [
{
"account_id": "IE74EXMP99000012345678",
"account_name": "Saltmarsh Language Services Ltd",
"bic": "EXMPIE2D"
}
]
},
"vat_exemptions": {
"AE": {
"reason": "Reverse charge (Article 196, Council Directive 2006/112/EC)",
"reason_code": "VATEX-EU-AE"
}
},
"lines": [
{
"quantity": "18400",
"unit": "D68",
"price": { "net": "0.12" },
"tax": { "category": "AE", "rate": "0" },
"item": {
"name": "Product catalogue translation, German to English",
"description": "Autumn/winter range, 412 product pages"
}
},
{
"quantity": "3250",
"unit": "D68",
"price": { "net": "0.14" },
"tax": { "category": "AE", "rate": "0" },
"item": {
"name": "Web shop interface localisation, German to English",
"description": "Checkout, customer account area and transactional e-mails"
}
},
{
"quantity": "1",
"unit": "LS",
"price": { "net": "450.00" },
"tax": { "category": "AE", "rate": "0" },
"item": {
"name": "Terminology glossary",
"description": "420 approved garden furniture terms with definitions and usage notes"
}
},
{
"quantity": "12.5",
"unit": "HUR",
"price": { "net": "58.50" },
"tax": { "category": "AE", "rate": "0" },
"item": {
"name": "In-context linguistic review",
"description": "Review on the staging shop, 38 corrections logged and verified"
}
},
{
"quantity": "24",
"unit": "ZP",
"price": { "net": "18.50" },
"tax": { "category": "AE", "rate": "0" },
"item": {
"name": "Brochure typesetting, English edition",
"description": "24-page printed brochure, print-ready PDF with bleed"
}
},
{
"quantity": "6",
"unit": "HUR",
"price": { "net": "65.00" },
"tax": { "category": "AE", "rate": "0" },
"item": {
"name": "Project management"
}
}
],
"notes": [
{
"text": "Supply of services to a business customer established in another EU Member State. No Irish VAT has been charged: the customer is responsible for accounting for VAT under the reverse-charge rules in their own country.",
"subject_code": "TXD"
},
{
"text": "Thank you for choosing Saltmarsh for the autumn/winter launch."
}
]
},
"services": "Translation · Localisation · Typesetting",
"website": "saltmarsh.example.com",
"bank_name": "Corrib Mutual Bank"
}Source
The body markup with Jinja, the stylesheet placed in <head> and the header and footer settings.
{#- Cross-border EU B2B invoice rendered from the EN 16931 invoice model (`invoice`, proto/einvoice/invoice.schema.json).
Lines in VAT category AE (reverse charge, rate 0) print the reverse-charge notice: the invoice notes with subject
code TXD plus the exemption reason from `vat_exemptions.AE`; lines in category S are charged at their rate.
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) and `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. The amount in words is built from whole euro and cents of
the amount due, so the words always match the figure. `services`, `website` and `bank_name` are display-only
extras outside the invoice model. -#}
{%- set inv = invoice -%}
{%- set cur = inv.currency -%}
{%- set loc = "en-IE" -%}
{%- 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"]} -%}
{%- set countries = {"IE": "Ireland", "DE": "Germany", "FR": "France", "NL": "Netherlands", "BE": "Belgium", "AT": "Austria", "ES": "Spain", "IT": "Italy", "PL": "Poland", "PT": "Portugal", "DK": "Denmark", "SE": "Sweden", "FI": "Finland", "LU": "Luxembourg"} -%}
{%- set profiles = {"MINIMUM": "Factur-X MINIMUM", "BASIC_WL": "Factur-X BASIC WL", "BASIC": "ZUGFeRD / Factur-X BASIC", "EN16931": "ZUGFeRD / Factur-X EN 16931", "EXTENDED": "ZUGFeRD / Factur-X EXTENDED", "XRECHNUNG": "XRechnung"} -%}
{%- set exemptions = {"Z": "zero rated", "E": "exempt", "AE": "reverse charge", "K": "intra-community supply", "G": "export", "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 reverse_charge = breakdown | selectattr("category", "equalto", "AE") | list | length > 0 -%}
{%- set supply_date = (inv.delivery.date if (inv.delivery and inv.delivery.date) else none) or inv.tax_point_date or (inv.invoice_period.end if inv.invoice_period else none) -%}
{%- set total_cents = ((totals.due_payable | float) * 100) | round | int -%}
{%- set whole_units = total_cents // 100 -%}
{%- set cents = total_cents % 100 -%}
{%- macro money(value) -%}{{ value | format_currency(cur, loc) }}{%- endmacro -%}
{%- macro day(value) -%}{{ value | format_date("long", loc) }}{%- endmacro -%}
{%- macro vat_chip(vat_id) -%}
<span class="vat-chip"><span class="cc">{{ vat_id[:2] }}</span>{{ vat_id[2:] }}</span>
{%- endmacro -%}
{%- macro address(party) -%}
{% if party.address.line1 %}{{ party.address.line1 }}<br>{% endif %}
{% if party.address.line2 %}{{ party.address.line2 }}<br>{% endif %}
{% if party.address.country in ["IE", "GB"] %}{{ party.address.city }} {{ party.address.postcode }}{% else %}{{ party.address.postcode }} {{ party.address.city }}{% endif %}<br>
{{ countries[party.address.country] or party.address.country }}
{%- endmacro -%}
{%- macro grouped(value) -%}
{%- for i in range(0, value | length, 4) -%}{{ value[i:i + 4] }}{{ " " if not loop.last }}{%- endfor -%}
{%- endmacro -%}
<header class="top">
<div class="brand">
<svg class="mark" viewBox="0 0 48 48" aria-hidden="true">
<circle cx="24" cy="24" r="22" fill="#1d5c52"/>
<path d="M9 27c4-4 8-4 11 0s7 4 10 0 7-4 9-1" fill="none" stroke="#ffffff" stroke-width="2.6" stroke-linecap="round"/>
<path d="M11 33c3.5-3 7-3 9.5 0s6.5 3 9 0 6-3 8 -0.5" fill="none" stroke="#9cc8bd" stroke-width="2.2" stroke-linecap="round"/>
<circle cx="31" cy="15.5" r="3.2" fill="#e8c26a"/>
</svg>
<div>
<div class="brand-name">{{ inv.seller.trading_name or inv.seller.name }}</div>
{% if services is defined and services %}<div class="brand-sub">{{ services }}</div>{% endif %}
</div>
</div>
<div class="title">
<h1>{{ "Credit note" if inv.type_code in ["381", "261"] else "Invoice" }}</h1>
<div class="title-no">No. {{ inv.number }}</div>
</div>
</header>
<table class="facts" role="presentation">
<tr>
<td><span class="k">Invoice date</span><span class="v">{{ day(inv.issue_date) }}</span></td>
<td><span class="k">Date of supply</span><span class="v">{{ day(supply_date) if supply_date else "—" }}</span></td>
<td><span class="k">Payment due</span><span class="v">{{ day(inv.due_date) if inv.due_date else "—" }}</span></td>
<td><span class="k">Your reference</span><span class="v">{{ inv.buyer_reference or inv.order_reference or "—" }}</span></td>
</tr>
</table>
<section class="parties">
<div class="party">
<h2>Supplier</h2>
<p class="party-name">{{ inv.seller.name }}</p>
<p>{{ address(inv.seller) }}</p>
{% if inv.seller.vat_id %}<p class="ids">VAT ID {{ vat_chip(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 class="party">
<h2>Customer</h2>
<p class="party-name">{{ inv.buyer.name }}</p>
{% if inv.buyer.contact and inv.buyer.contact.name %}<p class="muted">{{ inv.buyer.contact.name }}</p>{% endif %}
<p>{{ address(inv.buyer) }}</p>
{% if inv.buyer.vat_id %}<p class="ids">VAT ID {{ vat_chip(inv.buyer.vat_id) }}</p>{% endif %}
</div>
</section>
<table class="lines">
<thead>
<tr>
<th class="idx" scope="col">#</th>
<th scope="col">Description</th>
<th class="num" scope="col">Quantity</th>
<th class="num" scope="col">Unit price</th>
<th class="num" scope="col">VAT</th>
<th class="num" scope="col">Net amount</th>
</tr>
</thead>
<tbody>
{% for line in inv.lines %}
{%- set q = line.quantity | float -%}
{%- set names = units[line.unit or "C62"] -%}
<tr>
<td class="idx">{{ "%02d" | format(loop.index) }}</td>
<td>
<span class="desc">{{ line.item.name }}</span>
{% if line.item.description %}<span class="details">{{ line.item.description }}</span>{% endif %}
{% for a in line.allowances | default([]) %}<span class="details">Less {{ a.reason or "discount" }}: {{ money(a.amount) }}</span>{% endfor %}
{% for c in line.charges | default([]) %}<span class="details">Plus {{ c.reason or "charge" }}: {{ money(c.amount) }}</span>{% endfor %}
</td>
<td class="num">
{{ q | format_number(loc, 0 if q == q | int else 2) }}
<span class="unit">{% if names %}{{ names[0] if q == 1 else names[1] }}{% else %}{{ line.unit }}{% endif %}</span>
</td>
<td class="num">{{ money(line.price.net) }}</td>
<td class="num">{{ (line.tax.rate | default(0) | float) | format_number(loc) }}%</td>
<td class="num">{{ money(ns.nets[loop.index0]) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<section class="summary">
<div class="in-words">
<h2>Amount due in words</h2>
<p>
{{ whole_units | number_to_words("en") | capitalize }} {{ "euro" if cur == "EUR" else cur }}{% if cents %} and {{ cents | number_to_words("en") }} {{ "cent" if cur == "EUR" else "hundredths" }}{% endif %}
</p>
</div>
<table class="totals">
{% if (totals.allowance_total | float) != 0 or (totals.charge_total | float) != 0 %}
<tr><th scope="row">Sum of lines</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 %}
{% endif %}
<tr><th scope="row">Net total</th><td class="num">{{ money(totals.tax_basis_total) }}</td></tr>
{% for b in breakdown %}
<tr>
<th scope="row">VAT {{ (b.rate | float) | format_number(loc) }}%{% if b.category in exemptions %} · {{ exemptions[b.category] }}{% endif %}{% if breakdown | length > 1 %} on {{ money(b.basis) }}{% endif %}</th>
<td class="num">{{ money(b.tax) }}</td>
</tr>
{% endfor %}
{% if (totals.prepaid | float) != 0 %}
<tr><th scope="row">Total</th><td class="num">{{ money(totals.grand_total) }}</td></tr>
<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 %}
<tr class="grand"><th scope="row">Total due ({{ cur }})</th><td class="num">{{ money(totals.due_payable) }}</td></tr>
</table>
</section>
{% if reverse_charge %}
{%- set exemption = inv.vat_exemptions.AE if (inv.vat_exemptions and inv.vat_exemptions.AE) else none -%}
<aside class="notice">
<svg class="notice-icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M4 9h13l-3.5-3.5M20 15H7l3.5 3.5" fill="none" stroke="#1d5c52" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<div>
<p class="notice-title">Reverse charge</p>
{% for note in inv.notes | default([]) if note.subject_code == "TXD" %}<p>{{ note.text }}</p>{% endfor %}
{% if exemption and exemption.reason %}<p class="notice-basis">{{ exemption.reason }}</p>{% endif %}
<p class="notice-ids">Supplier VAT ID {{ inv.seller.vat_id }} · Customer VAT ID {{ inv.buyer.vat_id }}</p>
</div>
</aside>
{% endif %}
{% if inv.payment %}
<section class="payment">
<div class="pay-head">
<h2>Bank transfer</h2>
<p>Pay {{ money(totals.due_payable) }}{% if inv.due_date %} by {{ day(inv.due_date) }}{% endif %}</p>
</div>
<dl>
{% for account in inv.payment.credit_transfers | default([]) %}
<div><dt>Account name</dt><dd>{{ account.account_name or inv.seller.name }}</dd></div>
{% if loop.first and bank_name is defined and bank_name %}<div><dt>Bank</dt><dd>{{ bank_name }}</dd></div>{% endif %}
<div><dt>{{ "IBAN" if (account.account_id | length) >= 15 else "Account" }}</dt><dd class="mono">{{ grouped(account.account_id) }}</dd></div>
{% if account.bic %}<div><dt>BIC</dt><dd class="mono">{{ account.bic }}</dd></div>{% endif %}
{% endfor %}
<div><dt>Payment reference</dt><dd class="mono">{{ inv.payment.remittance_information or inv.number }}</dd></div>
</dl>
</section>
{% endif %}
<section class="notes">
{% if inv.payment_terms %}<p>{{ inv.payment_terms }}</p>{% endif %}
{% for note in inv.notes | default([]) if note.subject_code != "TXD" %}<p>{{ note.text }}</p>{% endfor %}
{% if embedded %}<p class="einvoice-hint">This PDF carries an embedded e-invoice ({{ profiles[embedded.profile] or embedded.profile }} XML).</p>{% endif %}
</section><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 {
--ink: #17252b;
--muted: #5a6a71;
--rule: #d3dcdf;
--soft: #f1f6f4;
--accent: #1d5c52;
--accent-soft: #e3efeb;
--gold: #c9962f;
}
* { box-sizing: border-box; }
html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
body {
margin: 0;
color: var(--ink);
font-family: "Noto Sans", "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); }
.mono { font-family: "DejaVu Sans Mono", "Liberation Mono", monospace; font-size: 8.5pt; letter-spacing: 0.02em; }
/* Header */
.top { display: flex; justify-content: space-between; align-items: flex-end; padding-bottom: 6mm; }
.brand { display: flex; align-items: center; gap: 3.5mm; }
.mark { width: 13mm; height: 13mm; }
.brand-name { font-family: "Noto Serif", "Liberation Serif", Georgia, serif; font-size: 14pt; font-weight: 700; color: var(--accent); }
.brand-sub { font-size: 8pt; color: var(--muted); letter-spacing: 0.04em; }
.title { text-align: right; }
.title h1 {
margin: 0; font-family: "Noto Serif", "Liberation Serif", Georgia, serif; font-weight: 400;
font-size: 30pt; line-height: 1; letter-spacing: 0.01em;
}
.title-no { margin-top: 1.5mm; font-size: 10pt; font-weight: 600; color: var(--accent); }
/* Key facts strip */
.facts { width: 100%; border-collapse: collapse; border-top: 0.6mm solid var(--accent); border-bottom: 0.25mm solid var(--rule); }
.facts td { width: 25%; padding: 3mm 3mm 3mm 0; vertical-align: top; }
.facts td + td { padding-left: 3mm; border-left: 0.25mm solid var(--rule); }
.facts .k { display: block; font-size: 7pt; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.facts .v { display: block; margin-top: 0.5mm; font-weight: 600; }
/* Parties */
.parties { display: grid; grid-template-columns: 1fr 1fr; gap: 10mm; margin: 7mm 0 8mm; }
.party h2 {
margin: 0 0 2mm; font-size: 7.5pt; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent);
}
.party p + p { margin-top: 1mm; }
.party-name { font-size: 10.5pt; font-weight: 700; }
.ids { padding-top: 1mm; }
.vat-chip {
display: inline-block; margin-left: 1mm; padding: 0.4mm 1.8mm 0.4mm 0; border: 0.25mm solid var(--accent);
border-radius: 1mm; font-family: "DejaVu Sans Mono", "Liberation Mono", monospace; font-size: 8.5pt; color: var(--ink);
}
.vat-chip .cc { display: inline-block; margin-right: 1.5mm; padding: 0 1.5mm; background: var(--accent); color: #ffffff; font-weight: 700; }
/* Line items */
table.lines { width: 100%; border-collapse: collapse; }
table.lines thead { display: table-header-group; }
table.lines th {
padding: 2mm; background: var(--accent); color: #ffffff; text-align: left;
font-size: 7.5pt; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
}
table.lines th.num { text-align: right; }
table.lines td { padding: 2.6mm 2mm; border-bottom: 0.2mm solid var(--rule); vertical-align: top; }
table.lines tbody tr { break-inside: avoid; page-break-inside: avoid; }
table.lines tbody tr:nth-child(even) td { background: var(--soft); }
.idx { width: 9mm; color: var(--muted); font-variant-numeric: tabular-nums; }
.desc { font-weight: 600; }
.details { display: block; margin-top: 0.5mm; font-size: 8pt; color: var(--muted); }
.unit { color: var(--muted); font-size: 8pt; }
/* Summary */
.summary {
display: grid; grid-template-columns: 1fr 76mm; gap: 10mm; align-items: start; margin-top: 6mm;
break-inside: avoid; page-break-inside: avoid;
}
.in-words { padding: 3.5mm 4mm; border-left: 0.8mm solid var(--gold); background: #fbf6ea; }
.in-words h2 { margin: 0 0 1mm; font-size: 7pt; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); font-weight: 600; }
.in-words p { font-family: "Noto Serif", "Liberation Serif", Georgia, serif; font-size: 10pt; font-style: italic; }
.totals { width: 100%; border-collapse: collapse; }
.totals td, .totals th { padding: 1.6mm 0; }
.totals th { color: var(--muted); font-weight: inherit; text-align: inherit; }
.totals .grand td, .totals .grand th { padding-top: 3mm; border-top: 0.5mm solid var(--ink); font-size: 12.5pt; font-weight: 700; color: var(--ink); }
/* Reverse-charge notice */
.notice {
display: flex; gap: 3.5mm; margin-top: 7mm; padding: 4mm 5mm; border: 0.3mm solid var(--accent);
border-radius: 1.5mm; background: var(--accent-soft); break-inside: avoid; page-break-inside: avoid;
}
.notice-icon { flex: none; width: 7mm; height: 7mm; }
.notice-title { font-weight: 700; color: var(--accent); text-transform: uppercase; letter-spacing: 0.08em; font-size: 8pt; margin-bottom: 0.8mm; }
.notice p + p { margin-top: 1mm; }
.notice-basis { font-size: 8pt; }
.notice-ids { margin-top: 1.2mm; font-size: 8pt; color: var(--muted); }
/* Payment */
.payment {
display: grid; grid-template-columns: 50mm 1fr; gap: 6mm; margin-top: 7mm; padding-top: 5mm;
border-top: 0.25mm solid var(--rule); break-inside: avoid; page-break-inside: avoid;
}
.pay-head h2 { margin: 0 0 1mm; font-family: "Noto Serif", "Liberation Serif", Georgia, serif; font-size: 12pt; color: var(--accent); }
.pay-head p { color: var(--muted); }
.payment dl { display: grid; grid-template-columns: 1fr 1fr; gap: 2.5mm 6mm; margin: 0; }
.payment dt { font-size: 7pt; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.payment dd { margin: 0.3mm 0 0; font-weight: 600; }
.notes { margin-top: 7mm; font-size: 8.5pt; color: var(--muted); }
.notes p { margin: 0 0 1.5mm; }
.notes strong { color: var(--ink); }
.notes .einvoice-hint { margin-top: 3mm; font-size: 7.5pt; }
</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": "SLS-2026-0412",
"issue_date": "2026-09-04",
"due_date": "2026-10-04",
"currency": "EUR",
"buyer_reference": "PO 4500-2291",
"payment_terms": "Payment within 30 days of the invoice date by SEPA credit transfer. Bank charges on international transfers are paid by the sender.",
"delivery": {
"date": "2026-08-31"
},
"seller": {
"name": "Saltmarsh Language Services Ltd",
"trading_name": "Saltmarsh Language Services",
"vat_id": "IE1234567T",
"legal_registration": {
"id": "123456"
},
"legal_info": "Registered in Ireland, company no. 123456",
"electronic_address": {
"id": "accounts@saltmarsh.example.com",
"scheme": "EM"
},
"address": {
"line1": "Unit 7, Claddagh Quay Business Park",
"city": "Galway",
"postcode": "H91 X2Y3",
"country": "IE"
},
"contact": {
"name": "Accounts",
"email": "accounts@saltmarsh.example.com"
}
},
"buyer": {
"name": "Lindgrün Gartenmöbel GmbH",
"vat_id": "DE123456789",
"electronic_address": {
"id": "rechnungen@lindgruen.example.com",
"scheme": "EM"
},
"address": {
"line1": "Hafenstraße 12",
"city": "Hamburg",
"postcode": "20457",
"country": "DE"
},
"contact": {
"name": "Katrin Vogelsang, Marketing & E-Commerce"
}
},
"payment": {
"means_code": "58",
"remittance_information": "SLS-2026-0412",
"credit_transfers": [
{
"account_id": "IE74EXMP99000012345678",
"account_name": "Saltmarsh Language Services Ltd",
"bic": "EXMPIE2D"
}
]
},
"vat_exemptions": {
"AE": {
"reason": "Reverse charge (Article 196, Council Directive 2006/112/EC)",
"reason_code": "VATEX-EU-AE"
}
},
"lines": [
{
"quantity": "18400",
"unit": "D68",
"price": {
"net": "0.12"
},
"tax": {
"category": "AE",
"rate": "0"
},
"item": {
"name": "Product catalogue translation, German to English",
"description": "Autumn/winter range, 412 product pages"
}
},
{
"quantity": "3250",
"unit": "D68",
"price": {
"net": "0.14"
},
"tax": {
"category": "AE",
"rate": "0"
},
"item": {
"name": "Web shop interface localisation, German to English",
"description": "Checkout, customer account area and transactional e-mails"
}
}
],
"notes": [
{
"text": "Supply of services to a business customer established in another EU Member State. No Irish VAT has been charged: the customer is responsible for accounting for VAT under the reverse-charge rules in their own country.",
"subject_code": "TXD"
},
{
"text": "Thank you for choosing Saltmarsh for the autumn/winter launch."
}
]
},
"services": "Translation · Localisation · Typesetting",
"website": "saltmarsh.example.com",
"bank_name": "Corrib Mutual Bank"
},
"output": {
"format": "pdf",
"filename": "invoice-eu-reverse-charge.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": "SLS-2026-0412",
"issue_date": "2026-09-04",
"due_date": "2026-10-04",
"currency": "EUR",
"buyer_reference": "PO 4500-2291",
"payment_terms": "Payment within 30 days of the invoice date by SEPA credit transfer. Bank charges on international transfers are paid by the sender.",
"delivery": {
"date": "2026-08-31"
},
"seller": {
"name": "Saltmarsh Language Services Ltd",
"trading_name": "Saltmarsh Language Services",
"vat_id": "IE1234567T",
"legal_registration": {
"id": "123456"
},
"legal_info": "Registered in Ireland, company no. 123456",
"electronic_address": {
"id": "accounts@saltmarsh.example.com",
"scheme": "EM"
},
"address": {
"line1": "Unit 7, Claddagh Quay Business Park",
"city": "Galway",
"postcode": "H91 X2Y3",
"country": "IE"
},
"contact": {
"name": "Accounts",
"email": "accounts@saltmarsh.example.com"
}
},
"buyer": {
"name": "Lindgrün Gartenmöbel GmbH",
"vat_id": "DE123456789",
"electronic_address": {
"id": "rechnungen@lindgruen.example.com",
"scheme": "EM"
},
"address": {
"line1": "Hafenstraße 12",
"city": "Hamburg",
"postcode": "20457",
"country": "DE"
},
"contact": {
"name": "Katrin Vogelsang, Marketing & E-Commerce"
}
},
"payment": {
"means_code": "58",
"remittance_information": "SLS-2026-0412",
"credit_transfers": [
{
"account_id": "IE74EXMP99000012345678",
"account_name": "Saltmarsh Language Services Ltd",
"bic": "EXMPIE2D"
}
]
},
"vat_exemptions": {
"AE": {
"reason": "Reverse charge (Article 196, Council Directive 2006/112/EC)",
"reason_code": "VATEX-EU-AE"
}
},
"lines": [
{
"quantity": "18400",
"unit": "D68",
"price": {
"net": "0.12"
},
"tax": {
"category": "AE",
"rate": "0"
},
"item": {
"name": "Product catalogue translation, German to English",
"description": "Autumn/winter range, 412 product pages"
}
},
{
"quantity": "3250",
"unit": "D68",
"price": {
"net": "0.14"
},
"tax": {
"category": "AE",
"rate": "0"
},
"item": {
"name": "Web shop interface localisation, German to English",
"description": "Checkout, customer account area and transactional e-mails"
}
}
],
"notes": [
{
"text": "Supply of services to a business customer established in another EU Member State. No Irish VAT has been charged: the customer is responsible for accounting for VAT under the reverse-charge rules in their own country.",
"subject_code": "TXD"
},
{
"text": "Thank you for choosing Saltmarsh for the autumn/winter launch."
}
]
},
"services": "Translation · Localisation · Typesetting",
"website": "saltmarsh.example.com",
"bank_name": "Corrib Mutual Bank"
},
"output": {
"format": "pdf",
"filename": "invoice-eu-reverse-charge.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": "SLS-2026-0412",
"issue_date": "2026-09-04",
"due_date": "2026-10-04",
"currency": "EUR",
"buyer_reference": "PO 4500-2291",
"payment_terms": "Payment within 30 days of the invoice date by SEPA credit transfer. Bank charges on international transfers are paid by the sender.",
"delivery": {
"date": "2026-08-31",
},
"seller": {
"name": "Saltmarsh Language Services Ltd",
"trading_name": "Saltmarsh Language Services",
"vat_id": "IE1234567T",
"legal_registration": {
"id": "123456",
},
"legal_info": "Registered in Ireland, company no. 123456",
"electronic_address": {
"id": "accounts@saltmarsh.example.com",
"scheme": "EM",
},
"address": {
"line1": "Unit 7, Claddagh Quay Business Park",
"city": "Galway",
"postcode": "H91 X2Y3",
"country": "IE",
},
"contact": {
"name": "Accounts",
"email": "accounts@saltmarsh.example.com",
},
},
"buyer": {
"name": "Lindgrün Gartenmöbel GmbH",
"vat_id": "DE123456789",
"electronic_address": {
"id": "rechnungen@lindgruen.example.com",
"scheme": "EM",
},
"address": {
"line1": "Hafenstraße 12",
"city": "Hamburg",
"postcode": "20457",
"country": "DE",
},
"contact": {
"name": "Katrin Vogelsang, Marketing & E-Commerce",
},
},
"payment": {
"means_code": "58",
"remittance_information": "SLS-2026-0412",
"credit_transfers": [
{
"account_id": "IE74EXMP99000012345678",
"account_name": "Saltmarsh Language Services Ltd",
"bic": "EXMPIE2D",
},
],
},
"vat_exemptions": {
"AE": {
"reason": "Reverse charge (Article 196, Council Directive 2006/112/EC)",
"reason_code": "VATEX-EU-AE",
},
},
"lines": [
{
"quantity": "18400",
"unit": "D68",
"price": {
"net": "0.12",
},
"tax": {
"category": "AE",
"rate": "0",
},
"item": {
"name": "Product catalogue translation, German to English",
"description": "Autumn/winter range, 412 product pages",
},
},
{
"quantity": "3250",
"unit": "D68",
"price": {
"net": "0.14",
},
"tax": {
"category": "AE",
"rate": "0",
},
"item": {
"name": "Web shop interface localisation, German to English",
"description": "Checkout, customer account area and transactional e-mails",
},
},
],
"notes": [
{
"text": "Supply of services to a business customer established in another EU Member State. No Irish VAT has been charged: the customer is responsible for accounting for VAT under the reverse-charge rules in their own country.",
"subject_code": "TXD",
},
{
"text": "Thank you for choosing Saltmarsh for the autumn/winter launch.",
},
],
},
"services": "Translation · Localisation · Typesetting",
"website": "saltmarsh.example.com",
"bank_name": "Corrib Mutual Bank",
},
"output": {
"format": "pdf",
"filename": "invoice-eu-reverse-charge.pdf",
},
},
timeout=90,
)
response.raise_for_status()
print(response.json())More invoices templates
- 210 × 297 mm
Bold brand invoice
A4A 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.
- 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
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.