Skip to content

210 × 297 mm

HR & employment

Expense claim

An A4 employee expense claim: receipts in other currencies are converted at their exchange rate, mileage is paid per mile, reclaimable VAT is worked out per line, and the totals, the spend by category and the amount due after an advance follow, with a QR code to the receipt images and three sign-off boxes.

A4 portraitLocale en-GBHTML + JinjaHeader & footer

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
  • Grouping
  • QR code
  • Repeating table headers
  • Header & footer
  • Page numbers
  • signature

Filters such as format_currency and qrcode are described in the template language reference.

Variables

Derived from the sample data: 5 top-level fields, 34 paths in total. Send the same shape as data.

PathTypeExample
companyobject
company.namestring"Tidewater Analytics Ltd"
company.addressstring"4th Floor, Quayside House ⏎ 12 Wapping Lane ⏎ …"
company.emailstring"expenses@tidewater.example.com"
company.currencystring"GBP"
employeeobject
employee.namestring"Priya Raman"
employee.numberstring"TA-0219"
employee.departmentstring"Client Services"
employee.cost_centrestring"CS-200"
employee.bank_account_endingstring"4821"
claimobject
claim.numberstring"EXP-2026-0917"
claim.period_startdate"2026-09-01"
claim.period_enddate"2026-09-30"
claim.submitted_ondate"2026-09-30"
claim.purposestring"Client workshops in Leeds and Amsterdam, and a…"
claim.advancenumber150
claim.mileage_ratenumber0.45
claim.receipts_urlstring"https://expenses.tidewater.example.com/claims/…"
claim.approverstring"Martin Kowalski"
claim.approver_rolestring"Head of Client Services"
linesarray<object> · 15
lines[].datedate"2026-09-02"
lines[].receipt?string"R01"
lines[].categorystring"Travel"
lines[].descriptionstring"Contactless fares, client meetings in the City"
lines[].projectstring"CLT-130"
lines[].currencystring"GBP"
lines[].amount?number14.7
lines[].vat_ratenumber0
lines[].fx_rate?number0.8612
lines[].miles?number64
notesstring"Foreign-currency receipts are converted at the…"

Sample data

Fictional records used for previews. Replace them with your own data when you call the API.

data.json
{
  "company": {
    "name": "Tidewater Analytics Ltd",
    "address": "4th Floor, Quayside House\n12 Wapping Lane\nLondon E1W 2XY",
    "email": "expenses@tidewater.example.com",
    "currency": "GBP"
  },
  "employee": {
    "name": "Priya Raman",
    "number": "TA-0219",
    "department": "Client Services",
    "cost_centre": "CS-200",
    "bank_account_ending": "4821"
  },
  "claim": {
    "number": "EXP-2026-0917",
    "period_start": "2026-09-01",
    "period_end": "2026-09-30",
    "submitted_on": "2026-09-30",
    "purpose": "Client workshops in Leeds and Amsterdam, and a site visit in Reading",
    "advance": 150.0,
    "mileage_rate": 0.45,
    "receipts_url": "https://expenses.tidewater.example.com/claims/EXP-2026-0917/receipts",
    "approver": "Martin Kowalski",
    "approver_role": "Head of Client Services"
  },
  "lines": [
    {
      "date": "2026-09-02",
      "receipt": "R01",
      "category": "Travel",
      "description": "Contactless fares, client meetings in the City",
      "project": "CLT-130",
      "currency": "GBP",
      "amount": 14.7,
      "vat_rate": 0
    },
    {
      "date": "2026-09-07",
      "receipt": "R02",
      "category": "Travel",
      "description": "Rail, London King's Cross to Leeds, return",
      "project": "CLT-104",
      "currency": "GBP",
      "amount": 126.0,
      "vat_rate": 0
    },
    {
      "date": "2026-09-07",
      "receipt": "R03",
      "category": "Accommodation",
      "description": "Hotel, Leeds, 1 night",
      "project": "CLT-104",
      "currency": "GBP",
      "amount": 138.0,
      "vat_rate": 20
    },
    {
      "date": "2026-09-08",
      "receipt": "R04",
      "category": "Subsistence",
      "description": "Working lunch on the workshop day, 3 people",
      "project": "CLT-104",
      "currency": "GBP",
      "amount": 52.8,
      "vat_rate": 20
    },
    {
      "date": "2026-09-08",
      "receipt": "R05",
      "category": "Subsistence",
      "description": "Breakfast at Leeds station",
      "project": "CLT-104",
      "currency": "GBP",
      "amount": 9.6,
      "vat_rate": 20
    },
    {
      "date": "2026-09-14",
      "receipt": "R06",
      "category": "Travel",
      "description": "Flight, London Heathrow to Amsterdam, return",
      "project": "CLT-117",
      "currency": "GBP",
      "amount": 214.8,
      "vat_rate": 0
    },
    {
      "date": "2026-09-15",
      "receipt": "R07",
      "category": "Travel",
      "description": "Taxi, Schiphol airport to client office",
      "project": "CLT-117",
      "currency": "EUR",
      "amount": 38.5,
      "fx_rate": 0.8612,
      "vat_rate": 0
    },
    {
      "date": "2026-09-15",
      "receipt": "R08",
      "category": "Accommodation",
      "description": "Hotel, Amsterdam, 2 nights",
      "project": "CLT-117",
      "currency": "EUR",
      "amount": 312.0,
      "fx_rate": 0.8612,
      "vat_rate": 0
    },
    {
      "date": "2026-09-16",
      "receipt": "R09",
      "category": "Subsistence",
      "description": "Dinner, Amsterdam",
      "project": "CLT-117",
      "currency": "EUR",
      "amount": 42.8,
      "fx_rate": 0.8612,
      "vat_rate": 0
    },
    {
      "date": "2026-09-16",
      "receipt": "R10",
      "category": "Travel",
      "description": "Train, Amsterdam Centraal to Schiphol airport",
      "project": "CLT-117",
      "currency": "EUR",
      "amount": 6.2,
      "fx_rate": 0.8612,
      "vat_rate": 0
    },
    {
      "date": "2026-09-21",
      "category": "Mileage",
      "description": "Client visit, Reading and back, own car",
      "project": "CLT-121",
      "currency": "GBP",
      "miles": 64,
      "vat_rate": 0
    },
    {
      "date": "2026-09-22",
      "receipt": "R11",
      "category": "Software",
      "description": "Survey tool, one-month licence",
      "project": "INT-002",
      "currency": "GBP",
      "amount": 24.0,
      "vat_rate": 20
    },
    {
      "date": "2026-09-23",
      "receipt": "R12",
      "category": "Office supplies",
      "description": "Printing of workshop handouts",
      "project": "CLT-117",
      "currency": "GBP",
      "amount": 18.6,
      "vat_rate": 20
    },
    {
      "date": "2026-09-24",
      "receipt": "R13",
      "category": "Training",
      "description": "Online course on survey design, one module",
      "project": "INT-002",
      "currency": "GBP",
      "amount": 45.0,
      "vat_rate": 20
    },
    {
      "date": "2026-09-28",
      "receipt": "R14",
      "category": "Phone",
      "description": "Roaming charges during the Amsterdam trip",
      "project": "CLT-117",
      "currency": "GBP",
      "amount": 12.0,
      "vat_rate": 20
    }
  ],
  "notes": "Foreign-currency receipts are converted at the rate on the card statement. Mileage is paid at 45p per mile for business journeys in your own car. Please keep the original receipts until the claim has been paid."
}

Source

The body markup with Jinja, the stylesheet placed in <head> and the header and footer settings.

{#- Employee expense claim. Each line is converted to the company currency (amount × fx_rate, or miles × the mileage
    rate), rounded to the cent; reclaimable VAT is worked out from the converted amount and the line's vat_rate
    (0 when no VAT can be reclaimed). Totals, the summary by category and the amount due after the advance all derive
    from the lines. -#}
{%- set loc = "en-GB" -%}
{%- set cur = company.currency -%}
{%- set symbols = {"GBP": "£", "EUR": "€", "USD": "US$", "CHF": "CHF "} -%}
{%- set ns = namespace(rows=[], total=0.0, vat=0.0) -%}
{%- for line in lines -%}
  {%- if line.miles -%}
    {%- set amount = (line.miles * claim.mileage_rate) | round(2) -%}
  {%- else -%}
    {%- set amount = (line.amount * (line.fx_rate | default(1))) | round(2) -%}
  {%- endif -%}
  {%- set rate = line.vat_rate | default(0) -%}
  {%- set vat = (amount * rate / (100 + rate)) | round(2) -%}
  {%- set ns.rows = ns.rows + [{"category": line.category, "amount": amount, "vat": vat}] -%}
  {%- set ns.total = ns.total + amount -%}
  {%- set ns.vat = ns.vat + vat -%}
{%- endfor -%}
{%- set total = ns.total | round(2) -%}
{%- set vat_total = ns.vat | round(2) -%}
{%- set advance = claim.advance | default(0) -%}
{%- set due = (total - advance) | round(2) -%}
{%- set cns = namespace(max=0.0) -%}
{%- for group in ns.rows | groupby("category") -%}{%- set s = group.list | sum_by("amount") -%}{%- if s > cns.max -%}{%- set cns.max = s -%}{%- endif -%}{%- endfor -%}

{%- macro money(value) -%}{{ value | format_currency(cur, loc) }}{%- endmacro -%}
{%- macro day(value) -%}{{ value | format_date("%-d %b %Y", loc) }}{%- endmacro -%}
{%- macro short_day(value) -%}{{ value | format_date("%-d %b", loc) }}{%- endmacro -%}

<header class="top">
  <div class="brand">
    <svg viewBox="0 0 44 44" aria-hidden="true">
      <rect width="44" height="44" rx="10" fill="#0f6e6e"/>
      <path d="M8 27c4.5-4 9-4 13.5 0s9 4 14.5 0" fill="none" stroke="#ffffff" stroke-width="3" stroke-linecap="round"/>
      <path d="M8 19c4.5-4 9-4 13.5 0s9 4 14.5 0" fill="none" stroke="#9fd8d3" stroke-width="3" stroke-linecap="round"/>
    </svg>
    <div>
      <p class="brand-name">{{ company.name }}</p>
      <p class="brand-address">{{ company.address | nl2br }}</p>
    </div>
  </div>
  <div class="doc">
    <h1>Expense claim</h1>
    <p class="number">{{ claim.number }}</p>
  </div>
</header>

<section class="info">
  <div>
    <p class="label">Claimant</p>
    <dl>
      <dt>Name</dt><dd>{{ employee.name }}</dd>
      <dt>Employee no.</dt><dd>{{ employee.number }}</dd>
      <dt>Department</dt><dd>{{ employee.department }}</dd>
      <dt>Cost centre</dt><dd>{{ employee.cost_centre }}</dd>
    </dl>
  </div>
  <div>
    <p class="label">Claim</p>
    <dl>
      <dt>Period</dt><dd>{{ day(claim.period_start) }} – {{ day(claim.period_end) }}</dd>
      <dt>Submitted</dt><dd>{{ day(claim.submitted_on) }}</dd>
      <dt>Purpose</dt><dd>{{ claim.purpose }}</dd>
      <dt>Approver</dt><dd>{{ claim.approver }}</dd>
    </dl>
  </div>
  {% if claim.receipts_url %}
  <div class="receipts">
    {{ claim.receipts_url | qrcode(size=160, margin=0, ecc="M", alt="QR code linking to the receipt images") }}
    Receipt images
  </div>
  {% endif %}
</section>

<section class="summary">
  <div class="tile"><p class="label">Total claimed</p><p class="value">{{ money(total) }}</p></div>
  <div class="tile"><p class="label">VAT reclaimable</p><p class="value">{{ money(vat_total) }}</p></div>
  <div class="tile"><p class="label">Advance received</p><p class="value">{{ money(advance) }}</p></div>
  <div class="tile due"><p class="label">{{ "Due to you" if due >= 0 else "To repay" }}</p><p class="value">{{ money(due | abs) }}</p></div>
</section>

<table class="lines">
  <thead>
    <tr>
      <th class="date" scope="col">Date</th>
      <th class="ref" scope="col">Ref.</th>
      <th class="cat" scope="col">Category</th>
      <th scope="col">Description</th>
      <th class="num original" scope="col">Original</th>
      <th class="num amount" scope="col">Amount</th>
      <th class="num vat" scope="col">VAT</th>
    </tr>
  </thead>
  <tbody>
    {% for line in lines %}
    {%- set row = ns.rows[loop.index0] %}
    <tr>
      <td class="date">{{ short_day(line.date) }}</td>
      <td class="ref">{{ line.receipt | default("–") }}</td>
      <td class="cat"><span class="chip">{{ line.category }}</span></td>
      <td>{{ line.description }}{% if line.project %}<span class="project">Project {{ line.project }}</span>{% endif %}</td>
      <td class="num original">
        {%- if line.miles -%}{{ line.miles | format_number(loc, 0) }} mi × {{ money(claim.mileage_rate) }}
        {%- elif line.currency and line.currency != cur -%}{{ symbols[line.currency] or (line.currency ~ " ") }}{{ line.amount | format_number(loc, 2) }} × {{ line.fx_rate | format_number(loc, 4) }}
        {%- else -%}–{%- endif -%}
      </td>
      <td class="num amount">{{ money(row.amount) }}</td>
      <td class="num vat">{{ money(row.vat) if row.vat else "–" }}</td>
    </tr>
    {% endfor %}
  </tbody>
</table>

<section class="bottom">
  <div>
    <p class="label">By category</p>
    {% for group in ns.rows | groupby("category") %}
    {%- set s = group.list | sum_by("amount") %}
    <div class="cat-row">
      <span>{{ group.grouper }}</span>
      <span class="cat-bar"><span style="width: {{ (s / cns.max * 100) | round(1) }}%"></span></span>
      <span class="num">{{ money(s) }}</span>
    </div>
    {% endfor %}
  </div>
  <table class="totals">
    <tr><th scope="row">Expenses ({{ lines | length }} items)</th><td class="num">{{ money(total) }}</td></tr>
    <tr><th scope="row" class="muted">of which reclaimable VAT</th><td class="num muted">{{ money(vat_total) }}</td></tr>
    <tr class="grand"><th scope="row">Total claimed</th><td class="num">{{ money(total) }}</td></tr>
    {% if advance %}<tr><th scope="row">Less advance received</th><td class="num">−{{ money(advance) }}</td></tr>{% endif %}
    <tr class="due"><th scope="row">{{ "Due to you" if due >= 0 else "To repay" }}</th><td class="num">{{ money(due | abs) }}</td></tr>
  </table>
</section>

{% if notes %}<p class="notes">{{ notes }}</p>{% endif %}

<section class="approvals">
  <div class="approval">
    <h2>Claimant</h2>
    <p>I confirm these expenses were incurred on company business and have not been claimed before.</p>
    <div class="sign"></div>
    <div class="sign-meta"><span>{{ employee.name }}</span><span>Date</span></div>
  </div>
  <div class="approval">
    <h2>Approved by</h2>
    <p>{{ claim.approver_role }}: checked against the expenses policy and the project budgets.</p>
    <div class="sign"></div>
    <div class="sign-meta"><span>{{ claim.approver }}</span><span>Date</span></div>
  </div>
  <div class="approval">
    <h2>Finance</h2>
    <p>{% if due >= 0 %}Pay {{ money(due) }} to the account ending {{ employee.bank_account_ending }}.{% else %}Recover {{ money(due | abs) }} through payroll.{% endif %}</p>
    <div class="sign"></div>
    <div class="sign-meta"><span>Paid on</span><span>Initials</span></div>
  </div>
</section>

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": {
    "company": {
      "name": "Tidewater Analytics Ltd",
      "address": "4th Floor, Quayside House\n12 Wapping Lane\nLondon E1W 2XY",
      "email": "expenses@tidewater.example.com",
      "currency": "GBP"
    },
    "employee": {
      "name": "Priya Raman",
      "number": "TA-0219",
      "department": "Client Services",
      "cost_centre": "CS-200",
      "bank_account_ending": "4821"
    },
    "claim": {
      "number": "EXP-2026-0917",
      "period_start": "2026-09-01",
      "period_end": "2026-09-30",
      "submitted_on": "2026-09-30",
      "purpose": "Client workshops in Leeds and Amsterdam, and a site visit in Reading",
      "advance": 150,
      "mileage_rate": 0.45,
      "receipts_url": "https://expenses.tidewater.example.com/claims/EXP-2026-0917/receipts",
      "approver": "Martin Kowalski",
      "approver_role": "Head of Client Services"
    },
    "lines": [
      {
        "date": "2026-09-02",
        "receipt": "R01",
        "category": "Travel",
        "description": "Contactless fares, client meetings in the City",
        "project": "CLT-130",
        "currency": "GBP",
        "amount": 14.7,
        "vat_rate": 0
      },
      {
        "date": "2026-09-07",
        "receipt": "R02",
        "category": "Travel",
        "description": "Rail, London King'\''s Cross to Leeds, return",
        "project": "CLT-104",
        "currency": "GBP",
        "amount": 126,
        "vat_rate": 0
      }
    ],
    "notes": "Foreign-currency receipts are converted at the rate on the card statement. Mileage is paid at 45p per mile for business journeys in your own car. Please keep the original receipts until the claim has been paid."
  },
  "output": {
    "format": "pdf",
    "filename": "expense-claim.pdf"
  }
}'