210 × 297 mm
HR & employment
Monthly timesheet
An A4 monthly timesheet that lists every day of the period grouped by ISO week, works out hours from start, end and break, credits leave and sickness, compares the total with the contracted hours, charts hours by activity and ends with employee, manager and payroll sign-off boxes.
Opens Dynamic Document API with this template preloaded in a free workspace.
Template language features
- Loops
- Conditionals
- Macros
- Filters
- Date formatting
- Number formatting
- Grouping
- 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, 30 paths in total. Send the same shape as data.
| Path | Type | Example |
|---|---|---|
| company | object | |
| company.name | string | "Copperbeech Facilities Ltd" |
| company.site | string | "Harbour Point Business Park, Bristol" |
| company.email | string | "payroll@copperbeech.example.com" |
| employee | object | |
| employee.name | string | "Daniel Okafor" |
| employee.number | string | "EMP-0472" |
| employee.role | string | "Maintenance Technician" |
| employee.department | string | "Estates and Facilities" |
| employee.manager | string | "Rachel Whitmore" |
| employee.manager_role | string | "Estates Manager" |
| employee.daily_hours | number | 7.5 |
| employee.weekly_hours | number | 37.5 |
| period | object | |
| period.label | string | "August 2026" |
| period.start | date | "2026-08-01" |
| period.end | date | "2026-08-31" |
| period.submitted_on | date | "2026-09-01" |
| codes | array<object> · 4 | |
| codes[].code | string | "PPM" |
| codes[].label | string | "Planned maintenance" |
| codes[].color | string | "#2f6f8f" |
| entries | array<object> · 22 | |
| entries[].date | date | "2026-08-03" |
| entries[].start? | string | "07:30" |
| entries[].end? | string | "16:00" |
| entries[].break_minutes? | number | 30 |
| entries[].code? | string | "PPM" |
| entries[].note? | string | "HVAC filter changes, Block A" |
| entries[].type? | string | "leave" |
Sample data
Fictional records used for previews. Replace them with your own data when you call the API.
{
"company": {
"name": "Copperbeech Facilities Ltd",
"site": "Harbour Point Business Park, Bristol",
"email": "payroll@copperbeech.example.com"
},
"employee": {
"name": "Daniel Okafor",
"number": "EMP-0472",
"role": "Maintenance Technician",
"department": "Estates and Facilities",
"manager": "Rachel Whitmore",
"manager_role": "Estates Manager",
"daily_hours": 7.5,
"weekly_hours": 37.5
},
"period": {
"label": "August 2026",
"start": "2026-08-01",
"end": "2026-08-31",
"submitted_on": "2026-09-01"
},
"codes": [
{
"code": "PPM",
"label": "Planned maintenance",
"color": "#2f6f8f"
},
{
"code": "REACT",
"label": "Reactive repairs",
"color": "#d17a22"
},
{
"code": "PROJ",
"label": "Projects",
"color": "#5b8c3a"
},
{
"code": "CALL",
"label": "Call-outs",
"color": "#8a4fa3"
}
],
"entries": [
{
"date": "2026-08-03",
"start": "07:30",
"end": "16:00",
"break_minutes": 30,
"code": "PPM",
"note": "HVAC filter changes, Block A"
},
{
"date": "2026-08-04",
"start": "07:30",
"end": "16:00",
"break_minutes": 30,
"code": "PPM",
"note": "Fire door inspections, levels 1–4"
},
{
"date": "2026-08-05",
"start": "07:30",
"end": "15:30",
"break_minutes": 30,
"code": "REACT",
"note": "Leak under sink, level 2 kitchen"
},
{
"date": "2026-08-06",
"start": "07:30",
"end": "15:30",
"break_minutes": 30,
"code": "PPM"
},
{
"date": "2026-08-07",
"start": "07:30",
"end": "15:00",
"break_minutes": 30,
"code": "PPM",
"note": "Finished early, agreed with R. Whitmore"
},
{
"date": "2026-08-10",
"start": "07:30",
"end": "15:30",
"break_minutes": 30,
"code": "PPM"
},
{
"date": "2026-08-11",
"start": "07:30",
"end": "17:00",
"break_minutes": 30,
"code": "PROJ",
"note": "LED lighting upgrade, car park"
},
{
"date": "2026-08-12",
"start": "07:30",
"end": "17:00",
"break_minutes": 30,
"code": "PROJ",
"note": "LED lighting upgrade, car park"
},
{
"date": "2026-08-13",
"type": "leave",
"note": "Annual leave"
},
{
"date": "2026-08-14",
"type": "leave",
"note": "Annual leave"
},
{
"date": "2026-08-17",
"start": "07:30",
"end": "15:30",
"break_minutes": 30,
"code": "REACT",
"note": "Lift alarm reset, Block B"
},
{
"date": "2026-08-18",
"start": "07:30",
"end": "15:30",
"break_minutes": 30,
"code": "PPM"
},
{
"date": "2026-08-19",
"start": "07:30",
"end": "15:30",
"break_minutes": 30,
"code": "PPM",
"note": "Emergency lighting test"
},
{
"date": "2026-08-20",
"type": "sick",
"note": "Sickness, self-certified"
},
{
"date": "2026-08-21",
"start": "07:30",
"end": "15:30",
"break_minutes": 30,
"code": "PPM"
},
{
"date": "2026-08-22",
"start": "09:00",
"end": "13:00",
"break_minutes": 0,
"code": "CALL",
"note": "Call-out: boiler fault, Block C"
},
{
"date": "2026-08-24",
"start": "07:30",
"end": "15:30",
"break_minutes": 30,
"code": "PPM"
},
{
"date": "2026-08-25",
"start": "07:30",
"end": "16:30",
"break_minutes": 30,
"code": "PROJ",
"note": "Car park lighting, snagging"
},
{
"date": "2026-08-26",
"start": "07:30",
"end": "16:30",
"break_minutes": 30,
"code": "PROJ"
},
{
"date": "2026-08-27",
"start": "07:30",
"end": "15:30",
"break_minutes": 30,
"code": "PPM"
},
{
"date": "2026-08-28",
"start": "07:30",
"end": "15:30",
"break_minutes": 30,
"code": "REACT",
"note": "Door closer replaced, main entrance"
},
{
"date": "2026-08-31",
"type": "holiday",
"note": "Summer bank holiday"
}
]
}Source
The body markup with Jinja, the stylesheet placed in <head> and the header and footer settings.
{#- Monthly timesheet. Every day of the period is listed and grouped by ISO week; worked hours come from start, end
and break (a shift past midnight wraps), so nothing is stored twice. Expected hours are the contracted daily hours
on each weekday that is not a public holiday; annual leave and sickness are credited at the daily hours, and the
balance is worked + credited − expected. Entries: {date, start, end, break_minutes, code, note} for work, or
{date, type: "leave" | "sick" | "holiday", note}. -#}
{%- set loc = "en-GB" -%}
{%- set daily = employee.daily_hours -%}
{%- set kinds = {"leave": "Annual leave", "sick": "Sick", "holiday": "Public holiday"} -%}
{%- set ns = namespace(days=[], worked=0.0, leave=0, sick=0, holidays=0, weekdays=0) -%}
{%- for i in range(0, 31) -%}
{%- set d = period.start | date_add(days=i) | string -%}
{%- if d <= period.end -%}
{%- set dow = d | format_date("%u") | int -%}
{%- set week = d | format_date("%G-%V") -%}
{%- if dow <= 5 -%}{%- set ns.weekdays = ns.weekdays + 1 -%}{%- endif -%}
{%- set matches = entries | selectattr("date", "equalto", d) | list -%}
{%- if not matches -%}
{%- set ns.days = ns.days + [{"date": d, "dow": dow, "week": week, "kind": "off" if dow > 5 else "missing", "hours": 0, "code": none, "entry": {}}] -%}
{%- endif -%}
{%- for e in matches -%}
{%- set kind = e.type | default("work") -%}
{%- if kind == "work" -%}
{%- set s = e.start.split(":") -%}
{%- set t = e.end.split(":") -%}
{%- set span = ((t[0] | int) * 60 + (t[1] | int)) - ((s[0] | int) * 60 + (s[1] | int)) -%}
{%- set span = span + 1440 if span < 0 else span -%}
{%- set hours = ((span - (e.break_minutes | default(0))) / 60) | round(2) -%}
{%- set ns.worked = ns.worked + hours -%}
{%- else -%}
{%- set hours = 0 -%}
{%- if dow <= 5 and kind == "leave" -%}{%- set ns.leave = ns.leave + 1 -%}{%- endif -%}
{%- if dow <= 5 and kind == "sick" -%}{%- set ns.sick = ns.sick + 1 -%}{%- endif -%}
{%- if dow <= 5 and kind == "holiday" -%}{%- set ns.holidays = ns.holidays + 1 -%}{%- endif -%}
{%- endif -%}
{%- set ns.days = ns.days + [{"date": d, "dow": dow, "week": week, "kind": kind, "hours": hours, "code": e.code if kind == "work" else none, "entry": e}] -%}
{%- endfor -%}
{%- endif -%}
{%- endfor -%}
{%- set expected = (ns.weekdays - ns.holidays) * daily -%}
{%- set credited = (ns.leave + ns.sick) * daily -%}
{%- set worked = ns.worked | round(2) -%}
{%- set balance = (worked + credited - expected) | round(2) -%}
{%- set cns = namespace(max=0.0) -%}
{%- for c in codes -%}{%- set h = ns.days | selectattr("code", "equalto", c.code) | sum_by("hours") -%}{%- if h > cns.max -%}{%- set cns.max = h -%}{%- endif -%}{%- endfor -%}
{%- macro h(value) -%}{{ value | format_number(loc, 2) }}{%- endmacro -%}
<section class="title">
<div>
<p class="kicker">{{ company.name }} · Timesheet</p>
<h1>{{ period.label }}</h1>
</div>
<dl class="who">
<dt>Employee</dt><dd>{{ employee.name }} ({{ employee.number }})</dd>
<dt>Role</dt><dd>{{ employee.role }}, {{ employee.department }}</dd>
<dt>Manager</dt><dd>{{ employee.manager }}</dd>
<dt>Contract</dt><dd>{{ h(employee.weekly_hours) }} hours a week, {{ h(daily) }} a day</dd>
</dl>
</section>
<section class="summary">
<div class="tile">
<p class="tile-label">Expected</p>
<p class="tile-value">{{ h(expected) }}</p>
<p class="tile-note">{{ ns.weekdays - ns.holidays }} working days</p>
</div>
<div class="tile">
<p class="tile-label">Worked</p>
<p class="tile-value">{{ h(worked) }}</p>
<p class="tile-note">{{ ns.days | selectattr("kind", "equalto", "work") | list | length }} shifts</p>
</div>
<div class="tile">
<p class="tile-label">Absence</p>
<p class="tile-value">{{ h(credited) }}</p>
<p class="tile-note">{{ ns.leave }} leave, {{ ns.sick }} sick {{ "day" if ns.sick == 1 else "days" }}</p>
</div>
<div class="tile balance {{ 'plus' if balance > 0 else ('minus' if balance < 0 else '') }}">
<p class="tile-label">Balance</p>
<p class="tile-value">{{ "+" if balance > 0 else "" }}{{ h(balance) }}</p>
<p class="tile-note">hours {{ "over" if balance >= 0 else "under" }} contract</p>
</div>
<div class="activity">
<p class="tile-label">Hours by activity</p>
{% for c in codes %}
{%- set ch = ns.days | selectattr("code", "equalto", c.code) | sum_by("hours") %}
<div class="activity-row">
<span class="activity-name"><span class="swatch" style="background: {{ c.color }}"></span>{{ c.label }}</span>
<span class="num">{{ h(ch) }}</span>
<span class="bar"><span style="width: {{ ((ch / cns.max * 100) if cns.max else 0) | round(1) }}%; background: {{ c.color }}"></span></span>
</div>
{% endfor %}
</div>
</section>
<table class="days">
<thead>
<tr>
<th scope="col">Date</th>
<th class="time" scope="col">Start</th>
<th class="time" scope="col">End</th>
<th class="num" scope="col">Break</th>
<th class="num hours" scope="col">Hours</th>
<th scope="col">Activity</th>
<th scope="col">Notes</th>
</tr>
</thead>
{% for week in ns.days | groupby("week") %}
<tbody>
<tr class="week-row">
<td colspan="7">Week {{ week.grouper.split("-")[1] | int }}<span class="range">{{ (week.list | first).date | format_date("%-d %b", loc) }} – {{ (week.list | last).date | format_date("%-d %b", loc) }}</span></td>
</tr>
{% for day in week.list %}
{%- set e = day.entry %}
<tr class="{{ day.kind }}{{ ' absence' if day.kind in ['leave', 'sick'] else '' }}">
<td class="date">{{ day.date | format_date("%a %-d %b", loc) }}</td>
{% if day.kind == "work" %}
{%- set code = codes | selectattr("code", "equalto", e.code) | first %}
<td class="time">{{ e.start }}</td>
<td class="time">{{ e.end }}</td>
<td class="num">{{ e.break_minutes | default(0) }} min</td>
<td class="num hours">{{ h(day.hours) }}</td>
<td>{% if code %}<span class="code" style="background: {{ code.color }}">{{ code.code }}</span>{% else %}{{ e.code }}{% endif %}</td>
<td class="note">{{ e.note | default("") }}</td>
{% elif day.kind == "off" %}
<td colspan="3">Weekend</td>
<td class="num hours">–</td>
<td></td>
<td></td>
{% elif day.kind == "missing" %}
<td colspan="3">No entry</td>
<td class="num hours">–</td>
<td></td>
<td></td>
{% else %}
<td colspan="3"><span class="kind {{ day.kind }}">{{ kinds[day.kind] or day.kind }}</span></td>
<td class="num hours">{% if day.kind != "holiday" and day.dow <= 5 %}({{ h(daily) }}){% else %}–{% endif %}</td>
<td></td>
<td class="note">{{ e.note | default("") }}</td>
{% endif %}
</tr>
{% endfor %}
{% if week.list | rejectattr("kind", "equalto", "off") | list | length > 0 %}
<tr class="subtotal">
<td colspan="4" class="label">Worked in week {{ week.grouper.split("-")[1] | int }}</td>
<td class="num hours">{{ h(week.list | sum_by("hours")) }}</td>
<td colspan="2"></td>
</tr>
{% endif %}
</tbody>
{% endfor %}
</table>
<table class="totals">
<tr><th scope="row">Hours worked</th><td class="num">{{ h(worked) }}</td></tr>
<tr><th scope="row">Leave and sickness credited ({{ ns.leave + ns.sick }} × {{ h(daily) }})</th><td class="num">{{ h(credited) }}</td></tr>
<tr><th scope="row">Expected hours ({{ ns.weekdays - ns.holidays }} × {{ h(daily) }})</th><td class="num">−{{ h(expected) }}</td></tr>
<tr class="grand"><th scope="row">Balance</th><td class="num">{{ "+" if balance > 0 else "" }}{{ h(balance) }}</td></tr>
</table>
<section class="signoff">
<div class="sign-box">
<h2>Employee</h2>
<p>I confirm that this timesheet is a true record of my hours in {{ period.label }}.</p>
<div class="sign-line"></div>
<div class="sign-meta"><span>{{ employee.name }}</span><span>Date</span></div>
</div>
<div class="sign-box">
<h2>Approved by</h2>
<p>Hours, call-outs and absences checked against the rota.</p>
<div class="sign-line"></div>
<div class="sign-meta"><span>{{ employee.manager }}, {{ employee.manager_role }}</span><span>Date</span></div>
</div>
<div class="sign-box payroll">
<h2>Payroll use</h2>
<div class="payroll-field"><p>Received</p><div class="write-line"></div></div>
<div class="payroll-field"><p>Pay run</p><div class="write-line"></div></div>
<div class="payroll-field"><p>Initials</p><div class="write-line"></div></div>
</div>
</section><title>Timesheet {{ period.label }} – {{ employee.name }}</title>
<style>
:root {
--ink: #1e2329;
--muted: #616a74;
--rule: #dde1e6;
--soft: #f4f6f8;
--weekend: #f7f7f5;
--accent: #23577a;
--good: #2d7a46;
--bad: #b2402d;
}
* { box-sizing: border-box; }
html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
body {
margin: 0;
color: var(--ink);
font-family: "Inter", "Liberation Sans", Arial, sans-serif;
font-size: 8.4pt;
line-height: 1.4;
}
p, h1, h2 { margin: 0; }
.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.muted { color: var(--muted); }
/* Title */
.title { display: flex; justify-content: space-between; align-items: flex-end; gap: 8mm; padding-bottom: 4mm; border-bottom: 0.5mm solid var(--ink); }
.kicker { font-size: 7.5pt; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent); }
.title h1 { margin-top: 1mm; font-size: 22pt; font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; }
.who { display: grid; grid-template-columns: auto auto; gap: 0.6mm 4mm; font-size: 8pt; }
.who dt { color: var(--muted); text-align: right; }
.who dd { margin: 0; font-weight: 600; }
/* Summary */
.summary { display: grid; grid-template-columns: repeat(4, 1fr) 1.6fr; gap: 3mm; margin: 5mm 0 6mm; break-inside: avoid; page-break-inside: avoid; }
.tile { padding: 3mm 3.5mm; border-radius: 1.5mm; background: var(--soft); }
.tile-label { font-size: 6.8pt; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.tile-value { margin-top: 0.8mm; font-size: 15pt; font-weight: 700; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }
.tile-note { font-size: 7pt; color: var(--muted); }
.tile.balance.plus .tile-value { color: var(--good); }
.tile.balance.minus .tile-value { color: var(--bad); }
.activity { padding: 3mm 3.5mm; border: 0.3mm solid var(--rule); border-radius: 1.5mm; }
.activity-row { display: grid; grid-template-columns: 1fr auto; column-gap: 2mm; row-gap: 0.5mm; margin-top: 1.1mm; font-size: 7.2pt; line-height: 1.2; }
.activity-name { display: flex; align-items: center; gap: 1.4mm; }
.swatch { display: inline-block; width: 2mm; height: 2mm; border-radius: 0.5mm; }
.bar { display: block; grid-column: 1 / -1; height: 1.4mm; border-radius: 0.7mm; background: #e8ebef; overflow: hidden; }
.bar span { display: block; height: 100%; border-radius: 0.7mm; }
/* Days */
table.days { width: 100%; border-collapse: collapse; }
table.days thead { display: table-header-group; }
table.days th {
padding: 1.6mm 1.8mm; background: var(--ink); color: #ffffff; text-align: left;
font-size: 6.6pt; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
}
table.days th.num { text-align: right; }
table.days td { padding: 1.25mm 1.8mm; border-bottom: 0.2mm solid var(--rule); vertical-align: middle; }
/* Cells paint white inside their padding, so the tagged PDF keeps them in the table structure (PDF/UA)
without painting over the row edges; the row colours below override it. */
table.days td { background: #ffffff content-box; }
table.days tr { break-inside: avoid; page-break-inside: avoid; }
.week-row { break-after: avoid; page-break-after: avoid; }
.week-row td { padding: 2.6mm 1.8mm 1.2mm; border-bottom: 0.35mm solid var(--ink); font-size: 7.6pt; font-weight: 700; }
.week-row .range { margin-left: 2mm; font-weight: 400; color: var(--muted); }
.subtotal { break-before: avoid; page-break-before: avoid; }
.subtotal td { padding-top: 1.4mm; padding-bottom: 2mm; border-bottom: none; font-weight: 700; }
.subtotal .label { text-align: right; font-weight: 600; color: var(--muted); }
tr.off td { background: var(--weekend); color: #9aa1a9; }
tr.absence td { background: #fbf6ec; }
tr.holiday td { background: #eef5f9; }
.date { width: 17mm; white-space: nowrap; font-variant-numeric: tabular-nums; }
.time { width: 13mm; }
.hours { width: 14mm; font-weight: 600; }
.code {
display: inline-block; min-width: 12mm; padding: 0.2mm 1.4mm; border-radius: 0.8mm; color: #ffffff;
font-size: 6.6pt; font-weight: 700; letter-spacing: 0.04em; text-align: center;
}
.kind { display: inline-block; padding: 0.2mm 1.4mm; border-radius: 0.8mm; border: 0.25mm solid currentColor; font-size: 6.6pt; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
.kind.leave { color: #9a6a12; }
.kind.sick { color: var(--bad); }
.kind.holiday { color: var(--accent); }
.note { color: var(--muted); }
/* Totals and sign-off */
.totals { width: 92mm; margin: 5mm 0 0 auto; border-collapse: collapse; break-inside: avoid; page-break-inside: avoid; }
.totals td, .totals th { padding: 1.3mm 2mm; }
.totals th { font-weight: inherit; text-align: left; }
.totals .grand td, .totals .grand th { border-top: 0.4mm solid var(--ink); font-size: 10pt; font-weight: 700; }
.signoff { display: grid; grid-template-columns: 1fr 1fr 0.8fr; gap: 6mm; margin-top: 8mm; break-inside: avoid; page-break-inside: avoid; }
.sign-box { padding: 3.5mm 4mm; border: 0.3mm solid var(--rule); border-radius: 1.5mm; }
.sign-box h2 { font-size: 7.4pt; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent); }
.sign-box p { margin-top: 1.2mm; font-size: 7.6pt; }
.sign-line { height: 12mm; border-bottom: 0.3mm solid var(--ink); }
.sign-meta { display: flex; justify-content: space-between; margin-top: 1mm; font-size: 7pt; color: var(--muted); }
.payroll { background: var(--soft); border-style: dashed; }
.payroll-field { display: grid; grid-template-columns: 14mm 1fr; gap: 2mm; align-items: end; margin-top: 2.6mm; }
.payroll-field p { margin: 0; font-size: 7.4pt; color: var(--muted); }
.write-line { height: 4mm; border-bottom: 0.25mm solid #b8bec6; }
</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": {
"company": {
"name": "Copperbeech Facilities Ltd",
"site": "Harbour Point Business Park, Bristol",
"email": "payroll@copperbeech.example.com"
},
"employee": {
"name": "Daniel Okafor",
"number": "EMP-0472",
"role": "Maintenance Technician",
"department": "Estates and Facilities",
"manager": "Rachel Whitmore",
"manager_role": "Estates Manager",
"daily_hours": 7.5,
"weekly_hours": 37.5
},
"period": {
"label": "August 2026",
"start": "2026-08-01",
"end": "2026-08-31",
"submitted_on": "2026-09-01"
},
"codes": [
{
"code": "PPM",
"label": "Planned maintenance",
"color": "#2f6f8f"
},
{
"code": "REACT",
"label": "Reactive repairs",
"color": "#d17a22"
}
],
"entries": [
{
"date": "2026-08-03",
"start": "07:30",
"end": "16:00",
"break_minutes": 30,
"code": "PPM",
"note": "HVAC filter changes, Block A"
},
{
"date": "2026-08-04",
"start": "07:30",
"end": "16:00",
"break_minutes": 30,
"code": "PPM",
"note": "Fire door inspections, levels 1–4"
}
]
},
"output": {
"format": "pdf",
"filename": "timesheet-monthly.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": {
"company": {
"name": "Copperbeech Facilities Ltd",
"site": "Harbour Point Business Park, Bristol",
"email": "payroll@copperbeech.example.com"
},
"employee": {
"name": "Daniel Okafor",
"number": "EMP-0472",
"role": "Maintenance Technician",
"department": "Estates and Facilities",
"manager": "Rachel Whitmore",
"manager_role": "Estates Manager",
"daily_hours": 7.5,
"weekly_hours": 37.5
},
"period": {
"label": "August 2026",
"start": "2026-08-01",
"end": "2026-08-31",
"submitted_on": "2026-09-01"
},
"codes": [
{
"code": "PPM",
"label": "Planned maintenance",
"color": "#2f6f8f"
},
{
"code": "REACT",
"label": "Reactive repairs",
"color": "#d17a22"
}
],
"entries": [
{
"date": "2026-08-03",
"start": "07:30",
"end": "16:00",
"break_minutes": 30,
"code": "PPM",
"note": "HVAC filter changes, Block A"
},
{
"date": "2026-08-04",
"start": "07:30",
"end": "16:00",
"break_minutes": 30,
"code": "PPM",
"note": "Fire door inspections, levels 1–4"
}
]
},
"output": {
"format": "pdf",
"filename": "timesheet-monthly.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": {
"company": {
"name": "Copperbeech Facilities Ltd",
"site": "Harbour Point Business Park, Bristol",
"email": "payroll@copperbeech.example.com",
},
"employee": {
"name": "Daniel Okafor",
"number": "EMP-0472",
"role": "Maintenance Technician",
"department": "Estates and Facilities",
"manager": "Rachel Whitmore",
"manager_role": "Estates Manager",
"daily_hours": 7.5,
"weekly_hours": 37.5,
},
"period": {
"label": "August 2026",
"start": "2026-08-01",
"end": "2026-08-31",
"submitted_on": "2026-09-01",
},
"codes": [
{
"code": "PPM",
"label": "Planned maintenance",
"color": "#2f6f8f",
},
{
"code": "REACT",
"label": "Reactive repairs",
"color": "#d17a22",
},
],
"entries": [
{
"date": "2026-08-03",
"start": "07:30",
"end": "16:00",
"break_minutes": 30,
"code": "PPM",
"note": "HVAC filter changes, Block A",
},
{
"date": "2026-08-04",
"start": "07:30",
"end": "16:00",
"break_minutes": 30,
"code": "PPM",
"note": "Fire door inspections, levels 1–4",
},
],
},
"output": {
"format": "pdf",
"filename": "timesheet-monthly.pdf",
},
},
timeout=90,
)
response.raise_for_status()
print(response.json())More hr & employment templates
- 210 × 297 mm
Arbeitszeugnis (employment reference)
A4A German qualified employment reference on letterhead: the template writes the opening, the overall rating, the conduct sentence and the closing formula from the rating and the reason for leaving, in the right grammatical forms, switches to an interim reference with one field, and ends with two signature lines.
- 210 × 297 mm
Expense claim
A4An 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.