Rendering
Tag: Templates Β· Version: v1 Β· Stability: π’ Stable
The render endpoints turn template + data into a PDF. They are the only
endpoints that accept either auth lane β a JWT (for previews from the platform)
or an X-Api-Key (for your backend).
| Endpoint | Use when |
|---|---|
POST /api/templates/{id}/render | You have a stored template; render it with fresh data. |
POST /api/render | You want a one-off render without persisting a template. |
~9.5 renders/sec sustained on a single box, p50 ~625 ms, p95 ~1.1 s, p99 ~1.3 s (measured on a laptop). Plenty of headroom for typical workloads.
Render a stored templateβ
POST /api/templates/{id}/render Β· JWT or API key Β· {id} is a UUID.
{
"data": {
"invoiceNumber": "INV-1024",
"total": "$4,200.00",
"lineItems": [{ "name": "Design", "qty": 1, "price": "$4,200.00" }]
}
}
| Field | Type | Required | Notes |
|---|---|---|---|
data | JSON object | β | Free-form; its keys resolve the template's {{tokens}}. |
theme | object | null | β | A tenant's brand colours for this render. See Brand colours. |
Returns the rendered PDF.
curl -X POST https://api.paperwright.dev/api/templates/<id>/render \
-H "X-Api-Key: <key>" \
-H "Content-Type: application/json" \
-d '{ "data": { "invoiceNumber": "INV-1024", "total": "$4,200.00" } }' \
--output invoice.pdf
Render inline (no stored template)β
POST /api/render Β· JWT or API key
{
"content": "<h1>Hello {{name}}</h1>",
"data": { "name": "World" },
"baseTemplateId": null
}
| Field | Type | Required | Notes |
|---|---|---|---|
content | string | β | The template body with {{tokens}}. |
data | JSON object | β | Values to resolve tokens against. |
baseTemplateId | UUID | null | β | Reuse a stored template's base PDF as the background of this inline render. |
theme | object | null | β | A tenant's brand colours for this render. See Brand colours. |
Brand colours (theme)β
Both render endpoints take an optional theme that fills the template's brand
colours for that one render β one stored template, rendered in each of your
tenants' colours. The theme is never stored; it applies to the render it is sent
with.
{
"data": { "invoiceNumber": "INV-1024", "total": "$4,200.00" },
"theme": { "primary": "#E11D48", "accent": "#0EA5E9" }
}
| Key | Type | Notes |
|---|---|---|
primary | string | null | #rgb or #rrggbb. Replaces the template's primary colour. |
accent | string | null | #rgb or #rrggbb. Replaces the template's accent colour. |
- Per key.
null, an empty string, or an omitted key means "keep the template's own colour" β a tenant with no brand colour still renders. - Readable text follows. Elements that use the template's "on primary" / "on accent" colours get black or white, whichever reads better on the colour you sent.
- No brand slots, no effect. A template whose elements don't use the brand
colours ignores
themeand renders as designed. See Templates β Brand colours for how a template opts in. - Validated first. A bad theme is rejected before the render starts, and a rejected request is not metered.
| Problem | Result |
|---|---|
A key other than primary / accent | 400 β theme has an unknown key "secondary"; allowed: primary, accent |
A colour that isn't #rgb / #rrggbb | 400 β theme.primary must be a hex colour like #E11D48 (or theme.accent β¦) |
curl -X POST https://api.paperwright.dev/api/templates/<id>/render \
-H "X-Api-Key: <key>" \
-H "Content-Type: application/json" \
-d '{ "data": { "invoiceNumber": "INV-1024" },
"theme": { "primary": "#E11D48" } }' \
--output invoice.pdf
Logos and other imagesβ
An image element's source can be a token. Bind it to a data field such as
{{logo}} and send the picture in data, either as a data URI or as bare
base64 (the image type is detected for PNG, JPEG, GIF, WebP, BMP and SVG):
{
"data": {
"logo": "iVBORw0KGgoAAAANSUhEUgAAβ¦"
}
}
A field that is missing or empty renders no image. Remote URLs are never fetched β the renderer has no network access β so send the file's bytes rather than a link.
Page sizeβ
The page comes from the template, not the request: a template stores its paper, and a render uses it. There are 22 presets (ISO A3βA6 and B5, US Letter, Legal, Tabloid, Executive and Half Letter, labels, envelopes, cards, a ticket and two receipt rolls), each in portrait or landscape, plus a custom size. Anything unknown falls back to A4. See Templates β Page size for the stored shape.
A receipt roll (80 mm or 58 mm) has a fixed width and renders as one page as tall as its content, rather than being split into A4-style pages. (A roll longer than 14,400 pt, the PDF readers' page limit, continues on a new page.)
Tables grow to fit their dataβ
A table row's height is a minimum. When a bound row's data wraps onto more lines than the designed height allows β a long description, or Arabic text β the row grows in the PDF instead of clipping, and the rows below move down with it. Header rows and rows that only hold fixed text keep their designed height.
Token grammarβ
Tokens resolve against the data object at render time:
{{field}} plain substitution
{{total | number}} number, default pattern "#,##0.##"
{{total | number:"#,##0.00"}} number, custom pattern
{{rate | number:"percent"}} number preset
{{issuedAt | date:"medium"}} date preset
{{issuedAt | date:"dd/MM/yyyy"}} custom .NET-style format
{{$today | date:"long"}} current date at render time
{{$now | datetime:"short"}} current time at render time
{{$page}} / {{$pages}} page number / total
Date presets are medium, short, long and iso; datetime also accepts
time and 24h. A custom date format uses .NET-style specifiers (yyyy, MMMM, dd,
HH, mm, tt, and 'literal' text).
Numbers use the number filter with a preset (integer, decimal,
percent) or a custom pattern: 0 and # for digits, , for grouping, . for
the decimal point, %, 'literal' text, and ; to give negative numbers their
own pattern. Rounding is half away from zero, applied to the value as written, so
1.005 formats as 1.01. A value that isn't a number prints unchanged. A table
column can apply the same format to every cell.
Security modelβ
The renderer runs with JavaScript disabled and default-deny network egress (no SSRF) β markdown and icon SVGs are sanitised. AI-authored or user-authored content renders safely through the same hardened pipeline.
Meteringβ
Every render is metered (best-effort β a metering failure never fails a render).
Renders are tagged by source (api vs preview); see Usage.