210 × 297 mm
Invoices
Hourly consulting invoice with timesheet
An A4 invoice for European consultancies that bill by the hour: EN 16931 invoice lines per project and role with budget used per project, a timesheet grouped by project with subtotals, 21% VAT, and the invoice 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
- Grouping
- 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: 5 top-level fields, 84 paths in total. Send the same shape as data.
| Path | Type | Example |
|---|---|---|
| invoice | object | |
| invoice.number | string | "LV-26-0311" |
| invoice.issue_date | date | "2026-09-15" |
| invoice.due_date | date | "2026-09-30" |
| invoice.currency | string | "EUR" |
| invoice.order_reference | string | "SWO-PO-7719" |
| invoice.contract_reference | string | "SOW-2026-03" |
| invoice.payment_terms | string | "Net 15 days" |
| invoice.invoice_period | object | |
| invoice.invoice_period.start | date | "2026-09-01" |
| invoice.invoice_period.end | date | "2026-09-14" |
| invoice.delivery | object | |
| invoice.delivery.date | date | "2026-09-14" |
| invoice.seller | object | |
| invoice.seller.name | string | "Larkspur & Vane Consulting B.V." |
| invoice.seller.vat_id | string | "NL123456789B01" |
| invoice.seller.legal_registration | object | |
| invoice.seller.legal_registration.id | string | "12345678" |
| invoice.seller.legal_registration.scheme | string | "0106" |
| invoice.seller.electronic_address | object | |
| invoice.seller.electronic_address.id | string | "billing@larkspurvane.example.com" |
| invoice.seller.electronic_address.scheme | string | "EM" |
| invoice.seller.address | object | |
| invoice.seller.address.line1 | string | "Suite 4.02, Aldermeerkade 118" |
| invoice.seller.address.city | string | "Rotterdam" |
| invoice.seller.address.postcode | string | "3011 AB" |
| invoice.seller.address.country | string | "NL" |
| invoice.seller.contact | object | |
| invoice.seller.contact.name | string | "Billing" |
| invoice.seller.contact.email | string | "billing@larkspurvane.example.com" |
| invoice.buyer | object | |
| invoice.buyer.name | string | "Sablewood Outfitters B.V." |
| invoice.buyer.vat_id | string | "NL987654321B01" |
| invoice.buyer.electronic_address | object | |
| invoice.buyer.electronic_address.id | string | "ap@sablewood.example.com" |
| invoice.buyer.electronic_address.scheme | string | "EM" |
| invoice.buyer.address | object | |
| invoice.buyer.address.line1 | string | "Graniethaven 23" |
| invoice.buyer.address.city | string | "Tilburg" |
| invoice.buyer.address.postcode | string | "5048 AB" |
| invoice.buyer.address.country | string | "NL" |
| invoice.buyer.contact | object | |
| invoice.buyer.contact.name | string | "Dana Verhoeven, Head of Operations" |
| invoice.payment | object | |
| invoice.payment.means_code | string | "58" |
| invoice.payment.remittance_information | string | "LV-26-0311" |
| invoice.payment.credit_transfers | array<object> · 1 | |
| invoice.payment.credit_transfers[].account_id | string | "NL74EXMP0123456789" |
| invoice.payment.credit_transfers[].account_name | string | "Larkspur & Vane Consulting B.V." |
| invoice.payment.credit_transfers[].bic | string | "EXMPNL2A" |
| invoice.lines | array<object> · 8 | |
| invoice.lines[].quantity | string | "4.5" |
| invoice.lines[].unit | string | "HUR" |
| invoice.lines[].price | object | |
| invoice.lines[].price.net | string | "210.00" |
| invoice.lines[].tax | object | |
| invoice.lines[].tax.category | string | "S" |
| invoice.lines[].tax.rate | string | "21" |
| invoice.lines[].item | object | |
| invoice.lines[].item.name | string | "Demand forecasting model – Principal" |
| invoice.lines[].item.attributes | array<object> · 2 | |
| invoice.lines[].item.attributes[].name | string | "Project" |
| invoice.lines[].item.attributes[].value | string | "DFM" |
| invoice.notes | array<object> · 2 | |
| invoice.notes[].text | string | "Time is recorded in 15-minute increments and b…" |
| tagline | string | "Operations and analytics advisory" |
| website | string | "larkspurvane.example.com" |
| bank_name | string | "Kademeer Bank" |
| timesheet | object | |
| timesheet.engagement | string | "Supply chain analytics programme" |
| timesheet.projects | array<object> · 3 | |
| timesheet.projects[].code | string | "DFM" |
| timesheet.projects[].name | string | "Demand forecasting model" |
| timesheet.projects[].budget_hours | number | 120 |
| timesheet.projects[].prior_hours | number | 64.5 |
| timesheet.entries | array<object> · 13 | |
| timesheet.entries[].date | date | "2026-09-01" |
| timesheet.entries[].project | string | "DFM" |
| timesheet.entries[].consultant | string | "Maren Okafor" |
| timesheet.entries[].role | string | "Principal" |
| timesheet.entries[].description | string | "Kick-off workshop with merchandising and finan…" |
| timesheet.entries[].hours | number | 3 |
| timesheet.entries[].rate | number | 210 |
| timesheet.entries[].amount | number | 630 |
Sample data
Fictional records used for previews. Replace them with your own data when you call the API.
{
"invoice": {
"number": "LV-26-0311",
"issue_date": "2026-09-15",
"due_date": "2026-09-30",
"currency": "EUR",
"order_reference": "SWO-PO-7719",
"contract_reference": "SOW-2026-03",
"payment_terms": "Net 15 days",
"invoice_period": {
"start": "2026-09-01",
"end": "2026-09-14"
},
"delivery": {
"date": "2026-09-14"
},
"seller": {
"name": "Larkspur & Vane Consulting B.V.",
"vat_id": "NL123456789B01",
"legal_registration": {
"id": "12345678",
"scheme": "0106"
},
"electronic_address": {
"id": "billing@larkspurvane.example.com",
"scheme": "EM"
},
"address": {
"line1": "Suite 4.02, Aldermeerkade 118",
"city": "Rotterdam",
"postcode": "3011 AB",
"country": "NL"
},
"contact": {
"name": "Billing",
"email": "billing@larkspurvane.example.com"
}
},
"buyer": {
"name": "Sablewood Outfitters B.V.",
"vat_id": "NL987654321B01",
"electronic_address": {
"id": "ap@sablewood.example.com",
"scheme": "EM"
},
"address": {
"line1": "Graniethaven 23",
"city": "Tilburg",
"postcode": "5048 AB",
"country": "NL"
},
"contact": {
"name": "Dana Verhoeven, Head of Operations"
}
},
"payment": {
"means_code": "58",
"remittance_information": "LV-26-0311",
"credit_transfers": [
{
"account_id": "NL74EXMP0123456789",
"account_name": "Larkspur & Vane Consulting B.V.",
"bic": "EXMPNL2A"
}
]
},
"lines": [
{
"quantity": "4.5",
"unit": "HUR",
"price": {
"net": "210.00"
},
"tax": {
"category": "S",
"rate": "21"
},
"item": {
"name": "Demand forecasting model – Principal",
"attributes": [
{
"name": "Project",
"value": "DFM"
},
{
"name": "Role",
"value": "Principal"
}
]
}
},
{
"quantity": "10",
"unit": "HUR",
"price": {
"net": "165.00"
},
"tax": {
"category": "S",
"rate": "21"
},
"item": {
"name": "Demand forecasting model – Senior consultant",
"attributes": [
{
"name": "Project",
"value": "DFM"
},
{
"name": "Role",
"value": "Senior consultant"
}
]
}
},
{
"quantity": "6.5",
"unit": "HUR",
"price": {
"net": "120.00"
},
"tax": {
"category": "S",
"rate": "21"
},
"item": {
"name": "Demand forecasting model – Data analyst",
"attributes": [
{
"name": "Project",
"value": "DFM"
},
{
"name": "Role",
"value": "Data analyst"
}
]
}
},
{
"quantity": "6.25",
"unit": "HUR",
"price": {
"net": "165.00"
},
"tax": {
"category": "S",
"rate": "21"
},
"item": {
"name": "Point-of-sale data feed – Senior consultant",
"attributes": [
{
"name": "Project",
"value": "POS"
},
{
"name": "Role",
"value": "Senior consultant"
}
]
}
},
{
"quantity": "9.5",
"unit": "HUR",
"price": {
"net": "120.00"
},
"tax": {
"category": "S",
"rate": "21"
},
"item": {
"name": "Point-of-sale data feed – Data analyst",
"attributes": [
{
"name": "Project",
"value": "POS"
},
{
"name": "Role",
"value": "Data analyst"
}
]
}
},
{
"quantity": "6.5",
"unit": "HUR",
"price": {
"net": "210.00"
},
"tax": {
"category": "S",
"rate": "21"
},
"item": {
"name": "Warehouse slotting review – Principal",
"attributes": [
{
"name": "Project",
"value": "WSR"
},
{
"name": "Role",
"value": "Principal"
}
]
}
},
{
"quantity": "3.75",
"unit": "HUR",
"price": {
"net": "165.00"
},
"tax": {
"category": "S",
"rate": "21"
},
"item": {
"name": "Warehouse slotting review – Senior consultant",
"attributes": [
{
"name": "Project",
"value": "WSR"
},
{
"name": "Role",
"value": "Senior consultant"
}
]
}
},
{
"quantity": "6",
"unit": "HUR",
"price": {
"net": "120.00"
},
"tax": {
"category": "S",
"rate": "21"
},
"item": {
"name": "Warehouse slotting review – Data analyst",
"attributes": [
{
"name": "Project",
"value": "WSR"
},
{
"name": "Role",
"value": "Data analyst"
}
]
}
}
],
"notes": [
{
"text": "Time is recorded in 15-minute increments and billed at the rates agreed in SOW-2026-03. Travel to the Tilburg distribution centre was pre-approved and will be invoiced separately at cost."
},
{
"text": "Questions about an entry? Email billing@larkspurvane.example.com."
}
]
},
"tagline": "Operations and analytics advisory",
"website": "larkspurvane.example.com",
"bank_name": "Kademeer Bank",
"timesheet": {
"engagement": "Supply chain analytics programme",
"projects": [
{
"code": "DFM",
"name": "Demand forecasting model",
"budget_hours": 120,
"prior_hours": 64.5
},
{
"code": "POS",
"name": "Point-of-sale data feed",
"budget_hours": 60,
"prior_hours": 22.25
},
{
"code": "WSR",
"name": "Warehouse slotting review",
"budget_hours": 40,
"prior_hours": 0
}
],
"entries": [
{
"date": "2026-09-01",
"project": "DFM",
"consultant": "Maren Okafor",
"role": "Principal",
"description": "Kick-off workshop with merchandising and finance; agree forecast horizons and accuracy targets",
"hours": 3.0,
"rate": 210.0,
"amount": 630.0
},
{
"date": "2026-09-02",
"project": "DFM",
"consultant": "Ines Castellano",
"role": "Data analyst",
"description": "Profile three years of weekly sales history; flag gaps, outliers and stock-outs",
"hours": 6.5,
"rate": 120.0,
"amount": 780.0
},
{
"date": "2026-09-03",
"project": "DFM",
"consultant": "Tobias Linde",
"role": "Senior consultant",
"description": "Build baseline seasonal model and holdout test set",
"hours": 5.25,
"rate": 165.0,
"amount": 866.25
},
{
"date": "2026-09-08",
"project": "DFM",
"consultant": "Tobias Linde",
"role": "Senior consultant",
"description": "Add promotion and weather features; compare forecast error by region",
"hours": 4.75,
"rate": 165.0,
"amount": 783.75
},
{
"date": "2026-09-10",
"project": "DFM",
"consultant": "Maren Okafor",
"role": "Principal",
"description": "Review interim results with the finance lead and agree the next iteration",
"hours": 1.5,
"rate": 210.0,
"amount": 315.0
},
{
"date": "2026-09-02",
"project": "POS",
"consultant": "Tobias Linde",
"role": "Senior consultant",
"description": "Map store transaction export fields to the warehouse schema",
"hours": 3.5,
"rate": 165.0,
"amount": 577.5
},
{
"date": "2026-09-04",
"project": "POS",
"consultant": "Ines Castellano",
"role": "Data analyst",
"description": "Write validation checks for nightly file loads",
"hours": 4.0,
"rate": 120.0,
"amount": 480.0
},
{
"date": "2026-09-09",
"project": "POS",
"consultant": "Tobias Linde",
"role": "Senior consultant",
"description": "Fix duplicate-receipt handling in the load job; add regression tests",
"hours": 2.75,
"rate": 165.0,
"amount": 453.75
},
{
"date": "2026-09-11",
"project": "POS",
"consultant": "Ines Castellano",
"role": "Data analyst",
"description": "Backfill August data and reconcile totals with store reports",
"hours": 5.5,
"rate": 120.0,
"amount": 660.0
},
{
"date": "2026-09-08",
"project": "WSR",
"consultant": "Maren Okafor",
"role": "Principal",
"description": "Site walk-through at the Tilburg distribution centre with shift leads",
"hours": 4.0,
"rate": 210.0,
"amount": 840.0
},
{
"date": "2026-09-09",
"project": "WSR",
"consultant": "Ines Castellano",
"role": "Data analyst",
"description": "Pick-frequency analysis by SKU velocity band",
"hours": 6.0,
"rate": 120.0,
"amount": 720.0
},
{
"date": "2026-09-11",
"project": "WSR",
"consultant": "Maren Okafor",
"role": "Principal",
"description": "Draft slotting recommendations and labour savings estimate",
"hours": 2.5,
"rate": 210.0,
"amount": 525.0
},
{
"date": "2026-09-14",
"project": "WSR",
"consultant": "Tobias Linde",
"role": "Senior consultant",
"description": "Travel-path simulation for the proposed pick-face layout",
"hours": 3.75,
"rate": 165.0,
"amount": 618.75
}
]
}
}Source
The body markup with Jinja, the stylesheet placed in <head> and the header and footer settings.
{#- Hourly consulting invoice rendered from the EN 16931 invoice model (`invoice`, proto/einvoice/invoice.schema.json).
Invoice lines are billed per project and consultant role (hours × the role's rate), tagged with the item
attributes "Project" (code) and "Role"; the detailed time entries and project budgets stay outside the model in
`timesheet` and back up those lines (their hours per project and role add up to the line quantities).
With `output.einvoice.invoice_path = "invoice"` (this template's default) the renderer embeds the invoice 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, 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. -#}
{%- set inv = invoice -%}
{%- set cur = inv.currency -%}
{%- set loc = "en-GB" -%}
{%- 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 countries = {"NL": "Netherlands", "BE": "Belgium", "DE": "Germany", "FR": "France", "LU": "Luxembourg", "AT": "Austria", "IE": "Ireland", "ES": "Spain", "IT": "Italy", "DK": "Denmark"} -%}
{%- 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 -%}
{#- One row per invoice line with its project code and role (item attributes), for grouping by project -#}
{%- set pns = namespace(rows=[]) -%}
{%- for line in inv.lines -%}
{%- set attrs = line.item.attributes | default([]) -%}
{%- set project_attr = attrs | selectattr("name", "equalto", "Project") | first -%}
{%- set role_attr = attrs | selectattr("name", "equalto", "Role") | first -%}
{%- set pns.rows = pns.rows + [{"project": project_attr.value if project_attr else line.item.name, "role": role_attr.value if role_attr else line.item.name, "line": line, "hours": (line.quantity | float) if line.unit == "HUR" else 0, "net": ns.nets[loop.index0]}] -%}
{%- endfor -%}
{%- set line_groups = pns.rows | group_by("project") -%}
{%- set total_hours = pns.rows | sum_by("hours") -%}
{%- set entry_groups = timesheet.entries | group_by("project") if (timesheet is defined and timesheet.entries) else [] -%}
{%- macro hours(value) -%}{{ value | format_number(loc, 2) }}{%- endmacro -%}
{%- macro money(value) -%}{{ value | format_currency(cur, loc) }}{%- endmacro -%}
{%- macro day(value) -%}{{ value | format_date("long", loc) }}{%- endmacro -%}
{%- macro project_of(code) -%}{%- set p = timesheet.projects | selectattr("code", "equalto", code) | first if timesheet is defined else none -%}{{ p.name if p else code }}{%- endmacro -%}
{%- macro grouped(value) -%}
{%- for i in range(0, value | length, 4) -%}{{ value[i:i + 4] }}{{ " " if not loop.last }}{%- endfor -%}
{%- endmacro -%}
{%- macro address(party) -%}
{% if party.address.line1 %}{{ party.address.line1 }}<br>{% endif %}
{% if party.address.line2 %}{{ party.address.line2 }}<br>{% endif %}
{{ party.address.postcode }} {{ party.address.city }}<br>
{{ countries[party.address.country] or party.address.country }}
{%- endmacro -%}
<header class="masthead">
<div class="brand">
<svg class="mark" viewBox="0 0 40 40" aria-hidden="true">
<rect width="40" height="40" rx="10" fill="#3b39a6"/>
<path d="M10 11v18h8" fill="none" stroke="#ffffff" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 11l5.5 18L32 11" fill="none" stroke="#c6c4f1" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<div>
<div class="brand-name">{{ inv.seller.trading_name or inv.seller.name }}</div>
{% if tagline is defined and tagline %}<div class="brand-tagline">{{ tagline }}</div>{% endif %}
</div>
</div>
<div class="doc-id">
<h1 class="kicker">{{ "Credit note" if inv.type_code in ["381", "261"] else "Invoice" }}</h1>
<div class="number">{{ inv.number }}</div>
</div>
</header>
<section class="overview">
<div class="due-card">
<div class="due-label">Amount due</div>
<div class="due-amount">{{ money(totals.due_payable) }}</div>
{% if inv.due_date %}<div class="due-date">Due {{ day(inv.due_date) }}</div>{% endif %}
{% if inv.payment_terms %}<div class="due-terms">{{ inv.payment_terms }}</div>{% endif %}
</div>
<dl class="facts">
{% if inv.invoice_period %}
<div>
<dt>Billing period</dt>
<dd>{{ inv.invoice_period.start | format_date("medium", loc) }} – {{ inv.invoice_period.end | format_date("medium", loc) }}</dd>
</div>
{% endif %}
<div>
<dt>Issued</dt>
<dd>{{ day(inv.issue_date) }}</dd>
</div>
<div>
<dt>Hours billed</dt>
<dd>{{ hours(total_hours) }} hours · {{ line_groups | length }} project{{ "s" if line_groups | length != 1 else "" }}</dd>
</div>
{% if inv.order_reference %}
<div>
<dt>Purchase order</dt>
<dd>{{ inv.order_reference }}</dd>
</div>
{% endif %}
</dl>
</section>
<section class="parties">
<div>
<p class="label">From</p>
<p class="strong">{{ inv.seller.name }}</p>
<p>{{ address(inv.seller) }}</p>
{% if inv.seller.contact %}<p class="muted">{{ inv.seller.contact.email }}{% if inv.seller.contact.phone %}<br>{{ inv.seller.contact.phone }}{% endif %}</p>{% endif %}
<p class="muted">{% if inv.seller.vat_id %}VAT {{ inv.seller.vat_id }}{% endif %}{% if inv.seller.legal_registration %}<br>{{ "KvK" if inv.seller.legal_registration.scheme == "0106" else "Reg. no." }} {{ inv.seller.legal_registration.id }}{% endif %}</p>
</div>
<div>
<p class="label">Bill to</p>
<p class="strong">{{ inv.buyer.name }}</p>
{% if inv.buyer.contact and inv.buyer.contact.name %}<p>{{ inv.buyer.contact.name }}</p>{% endif %}
<p>{{ address(inv.buyer) }}</p>
{% if inv.buyer.vat_id %}<p class="muted">VAT {{ inv.buyer.vat_id }}</p>{% endif %}
</div>
<div>
<p class="label">Engagement</p>
{% if timesheet is defined and timesheet.engagement %}<p class="strong">{{ timesheet.engagement }}</p>{% endif %}
{% if inv.contract_reference %}<p>Statement of work {{ inv.contract_reference }}</p>{% endif %}
{% if inv.project_reference %}<p>Project {{ inv.project_reference }}</p>{% endif %}
<p class="muted">Rates as agreed per consultant role; time billed in quarter hours.</p>
</div>
</section>
<section class="charges">
<h2 class="section-title">Charges by project</h2>
<table class="lines">
<thead>
<tr>
<th scope="col">Service</th>
<th class="num col-hours" scope="col">Hours</th>
<th class="num col-rate" scope="col">Rate</th>
<th class="num col-vat" scope="col">VAT</th>
<th class="num col-amount" scope="col">Net amount</th>
</tr>
</thead>
{% for group in line_groups %}
{%- set project = (timesheet.projects | selectattr("code", "equalto", group.grouper) | first) if timesheet is defined else none -%}
{%- set group_hours = group.list | sum_by("hours") -%}
<tbody class="project">
<tr class="project-head">
<td colspan="5">
<div class="project-row">
<div>
<span class="code">{{ group.grouper }}</span>
<span class="project-name">{{ project.name if project else group.grouper }}</span>
</div>
{% if project and project.budget_hours %}
{%- set used_hours = project.prior_hours + group_hours -%}
{%- set prior_pct = [project.prior_hours / project.budget_hours * 100, 100] | min -%}
{%- set period_pct = [group_hours / project.budget_hours * 100, 100 - prior_pct] | min -%}
<div class="budget">
<div class="bar"><span class="bar-prior" style="width: {{ prior_pct | round(2) }}%"></span><span class="bar-period" style="width: {{ period_pct | round(2) }}%"></span></div>
<div class="bar-caption">Budget {{ hours(used_hours) }} of {{ project.budget_hours }} h used · {{ (used_hours / project.budget_hours) | format_percent(loc) }}</div>
</div>
{% endif %}
</div>
</td>
</tr>
{% for row in group.list %}
<tr class="line">
<td>{{ row.role }}{% if row.line.item.description %}<span class="role">{{ row.line.item.description }}</span>{% endif %}</td>
<td class="num">{{ hours(row.line.quantity) }}</td>
<td class="num">{{ money(row.line.price.net) }}</td>
<td class="num">{% if row.line.tax.category in exemptions %}{{ exemptions[row.line.tax.category] }}{% else %}{{ (row.line.tax.rate | float) | format_number(loc) }}%{% endif %}</td>
<td class="num">{{ money(row.net) }}</td>
</tr>
{% endfor %}
</tbody>
{% endfor %}
</table>
<div class="charges-foot">
<p class="legend"><span class="swatch swatch-prior"></span>Previous invoices <span class="swatch swatch-period"></span>This invoice</p>
<table class="totals">
<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">{% if b.category in exemptions %}VAT · {{ exemptions[b.category] }}{% else %}VAT {{ (b.rate | float) | format_number(loc) }}%{% 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 %}
<tr class="grand-total"><th scope="row">Amount due</th><td class="num">{{ money(totals.due_payable) }}</td></tr>
</table>
</div>
</section>
{% if entry_groups %}
<h2 class="section-title">Time detail</h2>
<table class="entries">
<thead>
<tr>
<th class="col-date" scope="col">Date</th>
<th class="col-who" scope="col">Consultant</th>
<th scope="col">Work performed</th>
<th class="num col-hours" scope="col">Hours</th>
<th class="num col-rate" scope="col">Rate</th>
<th class="num col-amount" scope="col">Net amount</th>
</tr>
</thead>
{% for group in entry_groups %}
<tbody class="project">
<tr class="project-head">
<td colspan="6">
<span class="project-name">{{ project_of(group.grouper) }}</span>
<span class="project-count">{{ group.list | length }} entr{{ "ies" if group.list | length != 1 else "y" }}</span>
</td>
</tr>
{% for entry in group.list %}
<tr class="entry{% if loop.last %} last{% endif %}">
<td class="col-date">{{ entry.date | format_date("%a %d %b", loc) }}</td>
<td class="col-who">
{{ entry.consultant }}
{% if entry.role %}<span class="role">{{ entry.role }}</span>{% endif %}
</td>
<td>{{ entry.description }}</td>
<td class="num">{{ hours(entry.hours) }}</td>
<td class="num">{{ money(entry.rate) }}</td>
<td class="num">{{ money(entry.amount) }}</td>
</tr>
{% endfor %}
<tr class="subtotal">
<td colspan="3">Subtotal · {{ project_of(group.grouper) }}</td>
<td class="num">{{ hours(group.list | sum_by("hours")) }}</td>
<td class="blank"></td>
<td class="num">{{ money(group.list | sum_by("amount")) }}</td>
</tr>
</tbody>
{% endfor %}
<tbody class="grand">
<tr>
<td colspan="3">Total{% if inv.invoice_period %} for {{ inv.invoice_period.start | format_date("medium", loc) }} – {{ inv.invoice_period.end | format_date("medium", loc) }}{% endif %} (excl. VAT)</td>
<td class="num">{{ hours(timesheet.entries | sum_by("hours")) }}</td>
<td class="blank"></td>
<td class="num">{{ money(timesheet.entries | sum_by("amount")) }}</td>
</tr>
</tbody>
</table>
{% endif %}
<section class="closing">
{% if inv.payment %}
<div class="remit">
<h2>How to pay</h2>
<dl>
{% for account in inv.payment.credit_transfers | default([]) %}
{% if loop.first and bank_name is defined and bank_name %}<dt>Bank</dt><dd>{{ bank_name }}</dd>{% endif %}
<dt>Account name</dt><dd>{{ account.account_name or inv.seller.name }}</dd>
<dt>{{ "IBAN" if (account.account_id | length) >= 15 else "Account" }}</dt><dd class="mono">{{ grouped(account.account_id) }}</dd>
{% if account.bic %}<dt>BIC</dt><dd class="mono">{{ account.bic }}</dd>{% endif %}
{% endfor %}
<dt>Reference</dt><dd class="mono">{{ inv.payment.remittance_information or inv.number }}</dd>
{% if inv.due_date %}<dt>Due by</dt><dd>{{ day(inv.due_date) }}</dd>{% endif %}
</dl>
</div>
{% endif %}
<div class="notes">
<h2>Notes</h2>
{% for note in inv.notes | default([]) %}<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 %}
</div>
</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: #1b1d2a;
--muted: #646a7c;
--faint: #9ca1b1;
--rule: #dfe1ea;
--accent: #3b39a6;
--accent-deep: #29287d;
--accent-soft: #efeefb;
--accent-mid: #c6c4f1;
}
* { box-sizing: border-box; }
html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
body {
margin: 0;
color: var(--ink);
font-family: "Source Sans 3", "Liberation Sans", Arial, sans-serif;
font-size: 9.5pt;
line-height: 1.4;
}
p { margin: 0; }
.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.muted { color: var(--muted); }
.strong { font-weight: 600; }
.mono { font-family: "DejaVu Sans Mono", "Liberation Mono", monospace; font-size: 8.5pt; }
.label, .section-title {
font-size: 7.5pt; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase; color: var(--accent);
}
.label { margin-bottom: 1.2mm; }
.section-title { margin: 0 0 2.5mm; }
/* Masthead */
.masthead { display: flex; justify-content: space-between; align-items: center; }
.brand { display: flex; align-items: center; gap: 3.5mm; }
.mark { width: 12mm; height: 12mm; }
.brand-name { font-size: 13pt; font-weight: 700; letter-spacing: -0.01em; }
.brand-tagline { font-size: 8.5pt; color: var(--muted); }
.doc-id { text-align: right; }
.doc-id .kicker { margin: 0; font-size: 8pt; font-weight: 600; letter-spacing: 0.24em; text-transform: uppercase; color: var(--muted); }
.doc-id .number { font-size: 19pt; font-weight: 700; letter-spacing: -0.01em; line-height: 1.1; }
/* Overview */
.overview { display: grid; grid-template-columns: 66mm 1fr; gap: 7mm; margin: 6mm 0 5mm; }
.due-card { background: var(--accent); color: #ffffff; border-radius: 2.5mm; padding: 5mm 6mm; }
.due-label { font-size: 7.5pt; letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent-mid); }
.due-amount { font-size: 25pt; font-weight: 700; line-height: 1.15; margin: 1mm 0; font-variant-numeric: tabular-nums; }
.due-date { font-size: 9pt; color: #e4e3fa; }
.due-terms { font-size: 8pt; color: var(--accent-mid); }
.facts { display: grid; grid-template-columns: 1fr 1fr; gap: 3.5mm 6mm; margin: 0; align-content: center; }
.facts div { border-left: 0.6mm solid var(--accent-mid); padding-left: 3mm; }
.facts dt { font-size: 7.5pt; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; }
.facts dd { margin: 0.4mm 0 0; font-weight: 600; }
/* Parties */
.parties {
display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 7mm;
padding: 4mm 0; margin-bottom: 6mm; border-top: 0.25mm solid var(--rule); border-bottom: 0.25mm solid var(--rule);
overflow-wrap: anywhere;
}
.parties p + p { margin-top: 0.8mm; }
/* Charges by project (the invoice lines) */
.charges { margin-bottom: 8mm; }
table { width: 100%; border-collapse: collapse; }
thead { display: table-header-group; }
th {
font-size: 7.5pt; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted);
text-align: left; padding: 1.8mm 2mm; border-bottom: 0.35mm solid var(--ink);
}
th.num { text-align: right; }
.lines tbody { break-inside: avoid; page-break-inside: avoid; }
.lines .project-head td { padding: 2.6mm 2mm 1.4mm; border-bottom: 0.3mm solid var(--accent-mid); }
.project-row { display: flex; justify-content: space-between; align-items: center; gap: 6mm; }
.budget { width: 62mm; flex: none; }
.line td { padding: 1.5mm 2mm; border-bottom: 0.2mm solid var(--rule); }
.lines tbody tr.line:last-child td { border-bottom: none; }
.lines .col-hours { width: 16mm; }
.lines .col-rate { width: 22mm; }
.lines .col-vat { width: 14mm; }
.lines .col-amount { width: 26mm; }
.code {
display: inline-block; min-width: 9mm; margin-right: 1.5mm; padding: 0.3mm 1.2mm; border-radius: 1mm;
background: var(--accent-soft); color: var(--accent-deep); font-size: 7.5pt; font-weight: 700; text-align: center;
}
.bar { display: flex; height: 2.2mm; border-radius: 1.1mm; background: #eceef3; overflow: hidden; }
.bar-prior { background: var(--accent-mid); }
.bar-period { background: var(--accent); }
.bar-caption { margin-top: 0.8mm; font-size: 7.5pt; color: var(--muted); text-align: right; }
.charges-foot {
display: grid; grid-template-columns: 1fr 78mm; gap: 8mm; align-items: start; margin-top: 3mm;
padding-top: 2mm; border-top: 0.35mm solid var(--ink); break-inside: avoid; page-break-inside: avoid;
}
.legend { margin-top: 1.5mm; font-size: 7.5pt; color: var(--muted); }
.swatch { display: inline-block; width: 2.6mm; height: 2.6mm; border-radius: 0.6mm; margin: 0 1.2mm 0 0; vertical-align: -0.35mm; }
.swatch-period { margin-left: 3.5mm; }
.swatch-prior { background: var(--accent-mid); }
.swatch-period { background: var(--accent); }
.totals td, .totals th { padding: 1.2mm 2mm; }
.totals th { border-bottom: none; color: var(--muted); font-size: inherit; font-weight: inherit; letter-spacing: normal; text-transform: none; }
.totals .grand-total td, .totals .grand-total th { padding-top: 2.4mm; border-top: 0.25mm solid var(--rule); font-size: 11pt; font-weight: 700; color: var(--accent-deep); }
/* Time detail */
.entries td { padding: 1.6mm 2mm; vertical-align: top; }
.entries tr { break-inside: avoid; page-break-inside: avoid; }
.entries .col-date { width: 22mm; white-space: nowrap; }
.entries .col-who { width: 31mm; }
.entries .col-hours { width: 14mm; }
.entries .col-rate { width: 19mm; }
.entries .col-amount { width: 22mm; }
.role { display: block; font-size: 7.5pt; color: var(--muted); }
.entry td { border-bottom: 0.2mm solid var(--rule); }
.entry.last td { border-bottom: none; }
.project-head td { padding: 3.2mm 2mm 1.6mm; border-bottom: 0.3mm solid var(--accent-mid); break-after: avoid; page-break-after: avoid; }
.project-name { font-size: 10.5pt; font-weight: 700; color: var(--accent-deep); }
.project-count { margin-left: 2mm; font-size: 7.5pt; color: var(--muted); }
.subtotal td { background: var(--accent-soft); font-weight: 600; padding: 1.8mm 2mm; }
.grand td {
padding: 3.2mm 2mm; font-size: 11pt; font-weight: 700; border-top: 0.5mm solid var(--ink);
}
.grand tr { break-inside: avoid; page-break-inside: avoid; }
/* Empty cells paint white inside their padding, so the tagged PDF keeps them in the table structure (PDF/UA)
without painting over the row edges; row colours override it. */
.blank { background: #ffffff content-box; }
/* Closing */
.closing { display: grid; grid-template-columns: 92mm 1fr; gap: 9mm; margin-top: 9mm; break-inside: avoid; page-break-inside: avoid; }
.closing h2 { margin: 0 0 2mm; font-size: 7.5pt; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase; color: var(--accent); }
.remit { padding: 4mm 5mm; border: 0.3mm solid var(--rule); border-radius: 2mm; }
.remit dl { display: grid; grid-template-columns: 24mm 1fr; gap: 0.9mm 3mm; margin: 0; }
.remit dt { color: var(--muted); }
.remit dd { margin: 0; }
.notes { font-size: 9pt; color: var(--muted); padding-top: 4mm; }
.notes p { margin: 0 0 2mm; }
.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": "LV-26-0311",
"issue_date": "2026-09-15",
"due_date": "2026-09-30",
"currency": "EUR",
"order_reference": "SWO-PO-7719",
"contract_reference": "SOW-2026-03",
"payment_terms": "Net 15 days",
"invoice_period": {
"start": "2026-09-01",
"end": "2026-09-14"
},
"delivery": {
"date": "2026-09-14"
},
"seller": {
"name": "Larkspur & Vane Consulting B.V.",
"vat_id": "NL123456789B01",
"legal_registration": {
"id": "12345678",
"scheme": "0106"
},
"electronic_address": {
"id": "billing@larkspurvane.example.com",
"scheme": "EM"
},
"address": {
"line1": "Suite 4.02, Aldermeerkade 118",
"city": "Rotterdam",
"postcode": "3011 AB",
"country": "NL"
},
"contact": {
"name": "Billing",
"email": "billing@larkspurvane.example.com"
}
},
"buyer": {
"name": "Sablewood Outfitters B.V.",
"vat_id": "NL987654321B01",
"electronic_address": {
"id": "ap@sablewood.example.com",
"scheme": "EM"
},
"address": {
"line1": "Graniethaven 23",
"city": "Tilburg",
"postcode": "5048 AB",
"country": "NL"
},
"contact": {
"name": "Dana Verhoeven, Head of Operations"
}
},
"payment": {
"means_code": "58",
"remittance_information": "LV-26-0311",
"credit_transfers": [
{
"account_id": "NL74EXMP0123456789",
"account_name": "Larkspur & Vane Consulting B.V.",
"bic": "EXMPNL2A"
}
]
},
"lines": [
{
"quantity": "4.5",
"unit": "HUR",
"price": {
"net": "210.00"
},
"tax": {
"category": "S",
"rate": "21"
},
"item": {
"name": "Demand forecasting model – Principal",
"attributes": [
{
"name": "Project",
"value": "DFM"
},
{
"name": "Role",
"value": "Principal"
}
]
}
},
{
"quantity": "10",
"unit": "HUR",
"price": {
"net": "165.00"
},
"tax": {
"category": "S",
"rate": "21"
},
"item": {
"name": "Demand forecasting model – Senior consultant",
"attributes": [
{
"name": "Project",
"value": "DFM"
},
{
"name": "Role",
"value": "Senior consultant"
}
]
}
}
],
"notes": [
{
"text": "Time is recorded in 15-minute increments and billed at the rates agreed in SOW-2026-03. Travel to the Tilburg distribution centre was pre-approved and will be invoiced separately at cost."
},
{
"text": "Questions about an entry? Email billing@larkspurvane.example.com."
}
]
},
"tagline": "Operations and analytics advisory",
"website": "larkspurvane.example.com",
"bank_name": "Kademeer Bank",
"timesheet": {
"engagement": "Supply chain analytics programme",
"projects": [
{
"code": "DFM",
"name": "Demand forecasting model",
"budget_hours": 120,
"prior_hours": 64.5
},
{
"code": "POS",
"name": "Point-of-sale data feed",
"budget_hours": 60,
"prior_hours": 22.25
}
],
"entries": [
{
"date": "2026-09-01",
"project": "DFM",
"consultant": "Maren Okafor",
"role": "Principal",
"description": "Kick-off workshop with merchandising and finance; agree forecast horizons and accuracy targets",
"hours": 3,
"rate": 210,
"amount": 630
},
{
"date": "2026-09-02",
"project": "DFM",
"consultant": "Ines Castellano",
"role": "Data analyst",
"description": "Profile three years of weekly sales history; flag gaps, outliers and stock-outs",
"hours": 6.5,
"rate": 120,
"amount": 780
}
]
}
},
"output": {
"format": "pdf",
"filename": "invoice-hourly-timesheet.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": "LV-26-0311",
"issue_date": "2026-09-15",
"due_date": "2026-09-30",
"currency": "EUR",
"order_reference": "SWO-PO-7719",
"contract_reference": "SOW-2026-03",
"payment_terms": "Net 15 days",
"invoice_period": {
"start": "2026-09-01",
"end": "2026-09-14"
},
"delivery": {
"date": "2026-09-14"
},
"seller": {
"name": "Larkspur & Vane Consulting B.V.",
"vat_id": "NL123456789B01",
"legal_registration": {
"id": "12345678",
"scheme": "0106"
},
"electronic_address": {
"id": "billing@larkspurvane.example.com",
"scheme": "EM"
},
"address": {
"line1": "Suite 4.02, Aldermeerkade 118",
"city": "Rotterdam",
"postcode": "3011 AB",
"country": "NL"
},
"contact": {
"name": "Billing",
"email": "billing@larkspurvane.example.com"
}
},
"buyer": {
"name": "Sablewood Outfitters B.V.",
"vat_id": "NL987654321B01",
"electronic_address": {
"id": "ap@sablewood.example.com",
"scheme": "EM"
},
"address": {
"line1": "Graniethaven 23",
"city": "Tilburg",
"postcode": "5048 AB",
"country": "NL"
},
"contact": {
"name": "Dana Verhoeven, Head of Operations"
}
},
"payment": {
"means_code": "58",
"remittance_information": "LV-26-0311",
"credit_transfers": [
{
"account_id": "NL74EXMP0123456789",
"account_name": "Larkspur & Vane Consulting B.V.",
"bic": "EXMPNL2A"
}
]
},
"lines": [
{
"quantity": "4.5",
"unit": "HUR",
"price": {
"net": "210.00"
},
"tax": {
"category": "S",
"rate": "21"
},
"item": {
"name": "Demand forecasting model – Principal",
"attributes": [
{
"name": "Project",
"value": "DFM"
},
{
"name": "Role",
"value": "Principal"
}
]
}
},
{
"quantity": "10",
"unit": "HUR",
"price": {
"net": "165.00"
},
"tax": {
"category": "S",
"rate": "21"
},
"item": {
"name": "Demand forecasting model – Senior consultant",
"attributes": [
{
"name": "Project",
"value": "DFM"
},
{
"name": "Role",
"value": "Senior consultant"
}
]
}
}
],
"notes": [
{
"text": "Time is recorded in 15-minute increments and billed at the rates agreed in SOW-2026-03. Travel to the Tilburg distribution centre was pre-approved and will be invoiced separately at cost."
},
{
"text": "Questions about an entry? Email billing@larkspurvane.example.com."
}
]
},
"tagline": "Operations and analytics advisory",
"website": "larkspurvane.example.com",
"bank_name": "Kademeer Bank",
"timesheet": {
"engagement": "Supply chain analytics programme",
"projects": [
{
"code": "DFM",
"name": "Demand forecasting model",
"budget_hours": 120,
"prior_hours": 64.5
},
{
"code": "POS",
"name": "Point-of-sale data feed",
"budget_hours": 60,
"prior_hours": 22.25
}
],
"entries": [
{
"date": "2026-09-01",
"project": "DFM",
"consultant": "Maren Okafor",
"role": "Principal",
"description": "Kick-off workshop with merchandising and finance; agree forecast horizons and accuracy targets",
"hours": 3,
"rate": 210,
"amount": 630
},
{
"date": "2026-09-02",
"project": "DFM",
"consultant": "Ines Castellano",
"role": "Data analyst",
"description": "Profile three years of weekly sales history; flag gaps, outliers and stock-outs",
"hours": 6.5,
"rate": 120,
"amount": 780
}
]
}
},
"output": {
"format": "pdf",
"filename": "invoice-hourly-timesheet.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": "LV-26-0311",
"issue_date": "2026-09-15",
"due_date": "2026-09-30",
"currency": "EUR",
"order_reference": "SWO-PO-7719",
"contract_reference": "SOW-2026-03",
"payment_terms": "Net 15 days",
"invoice_period": {
"start": "2026-09-01",
"end": "2026-09-14",
},
"delivery": {
"date": "2026-09-14",
},
"seller": {
"name": "Larkspur & Vane Consulting B.V.",
"vat_id": "NL123456789B01",
"legal_registration": {
"id": "12345678",
"scheme": "0106",
},
"electronic_address": {
"id": "billing@larkspurvane.example.com",
"scheme": "EM",
},
"address": {
"line1": "Suite 4.02, Aldermeerkade 118",
"city": "Rotterdam",
"postcode": "3011 AB",
"country": "NL",
},
"contact": {
"name": "Billing",
"email": "billing@larkspurvane.example.com",
},
},
"buyer": {
"name": "Sablewood Outfitters B.V.",
"vat_id": "NL987654321B01",
"electronic_address": {
"id": "ap@sablewood.example.com",
"scheme": "EM",
},
"address": {
"line1": "Graniethaven 23",
"city": "Tilburg",
"postcode": "5048 AB",
"country": "NL",
},
"contact": {
"name": "Dana Verhoeven, Head of Operations",
},
},
"payment": {
"means_code": "58",
"remittance_information": "LV-26-0311",
"credit_transfers": [
{
"account_id": "NL74EXMP0123456789",
"account_name": "Larkspur & Vane Consulting B.V.",
"bic": "EXMPNL2A",
},
],
},
"lines": [
{
"quantity": "4.5",
"unit": "HUR",
"price": {
"net": "210.00",
},
"tax": {
"category": "S",
"rate": "21",
},
"item": {
"name": "Demand forecasting model – Principal",
"attributes": [
{
"name": "Project",
"value": "DFM",
},
{
"name": "Role",
"value": "Principal",
},
],
},
},
{
"quantity": "10",
"unit": "HUR",
"price": {
"net": "165.00",
},
"tax": {
"category": "S",
"rate": "21",
},
"item": {
"name": "Demand forecasting model – Senior consultant",
"attributes": [
{
"name": "Project",
"value": "DFM",
},
{
"name": "Role",
"value": "Senior consultant",
},
],
},
},
],
"notes": [
{
"text": "Time is recorded in 15-minute increments and billed at the rates agreed in SOW-2026-03. Travel to the Tilburg distribution centre was pre-approved and will be invoiced separately at cost.",
},
{
"text": "Questions about an entry? Email billing@larkspurvane.example.com.",
},
],
},
"tagline": "Operations and analytics advisory",
"website": "larkspurvane.example.com",
"bank_name": "Kademeer Bank",
"timesheet": {
"engagement": "Supply chain analytics programme",
"projects": [
{
"code": "DFM",
"name": "Demand forecasting model",
"budget_hours": 120,
"prior_hours": 64.5,
},
{
"code": "POS",
"name": "Point-of-sale data feed",
"budget_hours": 60,
"prior_hours": 22.25,
},
],
"entries": [
{
"date": "2026-09-01",
"project": "DFM",
"consultant": "Maren Okafor",
"role": "Principal",
"description": "Kick-off workshop with merchandising and finance; agree forecast horizons and accuracy targets",
"hours": 3,
"rate": 210,
"amount": 630,
},
{
"date": "2026-09-02",
"project": "DFM",
"consultant": "Ines Castellano",
"role": "Data analyst",
"description": "Profile three years of weekly sales history; flag gaps, outliers and stock-outs",
"hours": 6.5,
"rate": 120,
"amount": 780,
},
],
},
},
"output": {
"format": "pdf",
"filename": "invoice-hourly-timesheet.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
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.