Templates / Report
Report
Title block, numbered sections, key-figure table from JSON, and a running footer. Sections come as JSON so an agent can write the prose and let the layout stay fixed.
1 page, A4. Rendered in 55 ms. 8 inputs, all optional.
Inputs
Everything variable comes in through sys.inputs. Missing keys fall back to sensible defaults, so an agent can send only what it knows.
| Key | Sample value |
|---|---|
author | Meridian Studio, Operations |
date | 28 September 2026 |
kpis | JSON, 249 chars |
logo | logo.png |
sections | JSON, 859 chars |
subtitle | Prepared for the management board |
summary | Rendering volume grew 18 % quarter over quarter while p95 latency fell below 220 ms. Two incidents, both resolved within the SLA. The EU region carried 91 % of traffic. |
title | Q3 2026 Operations Report |
Render it
After copying the template into your account:
curl -X POST https://api.foliant.dev/v1/render \
-H "Authorization: Bearer $FOLIANT_API_KEY" \
-H "Content-Type: application/json" \
-o report.pdf -d @- <<'JSON'
{ "template": "report.typ",
"inputs": {
"author": "Meridian Studio, Operations",
"date": "28 September 2026",
"kpis": "[{\"name\":\"Pages rendered\",\"value\":\"1 204 331\",\"change\":\"+18 %\"},{\"name\":\"Active accounts\",\"value\":\"2 870\",\"change\":\"+11 %\"},{\"name\":\"p95 render latency\",\"value\":\"212 ms\",\"change\":\"-9 %\"},{\"name\":\"Availability\",\"value\":\"99.97 %\",\"change\":\"+0.02 pt\"}]",
"logo": "logo.png",
"sections": "[{\"title\":\"Volume and growth\",\"text\":\"Growth was driven by agent integrations: 64 % of renders in September came through the MCP endpoint, up from 41 % in June. Invoice and report templates account for half of all stored templates.\"},{\"title\":\"Reliability\",\"text\":\"Both incidents were caused by a dependency timeout in the US region and were mitigated by the health-check failover within four minutes. The permanent fix (regional secret caching) shipped on 12 September.\"},{\"title\":\"Costs\",\"text\":\"Infrastructure cost per thousand pages fell to 0.31 EUR. Stripe fees remain the largest variable cost at 2.9 % plus 0.25 EUR per transaction; moving small accounts to monthly plans reduces the fixed component.\"},{\"title\":\"Outlook\",\"text\":\"Q4 priorities: template library launch, connector directory listings, and a LaTeX engine behind a separate price point.\"}]",
"subtitle": "Prepared for the management board",
"summary": "Rendering volume grew 18 % quarter over quarter while p95 latency fell below 220 ms. Two incidents, both resolved within the SLA. The EU region carried 91 % of traffic.",
"title": "Q3 2026 Operations Report"
} }
JSON
Through MCP, render_document with template: "report.typ" and the same inputs; get_template or fetch lists the keys.
Source
template.typ, 30 lines
#let input(k, default: "") = sys.inputs.at(k, default: default)
#let kpis = json(bytes(input("kpis", default: "[]")))
#let sections = json(bytes(input("sections", default: "[]")))
#let logo = input("logo")
#set page(paper: "a4", margin: (top: 24mm, bottom: 24mm, x: 24mm),
footer: context [#set text(size: 8.5pt, fill: luma(110)); #input("title") #h(1fr) #counter(page).display() / #counter(page).final().first()])
#set text(font: "Libertinus Serif", size: 11pt, fill: rgb("1a1917"))
#set par(justify: true, leading: 0.7em)
#set heading(numbering: "1.")
#show heading.where(level: 1): it => block(above: 9mm, below: 3mm)[#text(size: 14pt, weight: 600)[#counter(heading).display() #h(4pt) #it.body]]
#if logo != "" { image(logo, width: 36mm); v(6mm) }
#text(size: 24pt, weight: 600)[#input("title")]
#v(2mm)
#text(size: 12pt, fill: luma(110))[#input("subtitle")]
#v(1mm)
#text(size: 10pt, fill: luma(110))[#input("author") · #input("date")]
#v(4mm)
#line(length: 100%, stroke: 0.6pt)
#v(6mm)
#block(fill: rgb("f3efe6"), inset: 12pt, width: 100%, radius: 2pt)[#text(weight: 600)[Summary] \ #input("summary")]
#v(4mm)
#table(columns: (1fr, auto, auto), align: (left, right, right), inset: (y: 7pt),
stroke: (x, y) => if y == 0 { (bottom: 0.7pt) } else { (bottom: 0.3pt + luma(200)) },
[_Key figure_], [_This quarter_], [_Change_],
..kpis.map(k => (k.name, k.value, if k.change.starts-with("-") { text(fill: rgb("b8341c"))[#k.change] } else { k.change })).flatten())
#for s in sections [
= #s.title
#s.text
]