Documentation
API reference
Create template IDs, publish immutable versions, and render PDFs.
Set a base URL and admin token for the examples:
base=http://127.0.0.1:8080
admin_token='replace-with-your-admin-token'
Create a template ID
curl --fail-with-body -X POST "$base/v1/admin/templates" \
-H 'content-type: application/json' \
-H "x-admin-token: $admin_token" \
--data '{"id":"invoice"}'
IDs and versions start with an alphanumeric character. They can contain letters, numbers, periods, underscores, and hyphens.
Publish a version
A publish request carries everything the version needs to render, so a render never depends on host files.
| Field | Required | Contents |
|---|---|---|
schema |
yes | JSON Schema that every render request’s data is checked against |
source |
yes | Base64 Typst files by name. Must contain main.typ |
fonts |
yes | Base64 font files by name. At least one must be decodable |
static_assets |
no | Base64 files shipped with the version, read as ../static/name |
request_assets |
no | Allowlist of asset names a render request may supply |
Inside Typst, request data is sys.inputs.at("data") and request asset paths are
sys.inputs.at("assets"). This example publishes the invoice template shipped in the
repository, which uses both.
main=$(base64 < templates/invoice-v1.typ | tr -d '\n')
font=$(base64 < assets/fonts/NotoSans-Regular.ttf | tr -d '\n')
jq -n --arg main "$main" --arg font "$font" '
{
schema: {
type: "object",
additionalProperties: false,
required: ["number", "customer", "amount"],
properties: {
number: {type: "string"},
customer: {type: "string"},
amount: {type: "string"}
}
},
source: {"main.typ": $main},
fonts: {"NotoSans-Regular.ttf": $font},
request_assets: ["logo.png"]
}
' > invoice-v1.json
curl --fail-with-body -X POST "$base/v1/admin/templates/invoice/versions/v1" \
-H 'content-type: application/json' \
-H "x-admin-token: $admin_token" \
--data-binary @invoice-v1.json
Published content cannot change or be deleted. Publishing over an existing version
returns 409; publish a new version name instead.
Retire and restore a version
Retirement is an administrative lifecycle change. It keeps the immutable version and its objects durable, but stops rendering it and frees an active-version slot.
curl --fail-with-body -X POST \
-H "x-admin-token: $admin_token" \
"$base/v1/admin/templates/invoice/versions/v1/retire"
curl --fail-with-body -X POST \
-H "x-admin-token: $admin_token" \
"$base/v1/admin/templates/invoice/versions/v1/unretire"
Both successful calls return 204 No Content. A retired version stays in PostgreSQL
and S3 but its render request returns 410 Gone. Unretirement checksum-verifies the
stored pack and can return 503 when an active-version limit would be exceeded.
Retirement does not free retained-byte capacity.
Render a PDF
GET /metrics is not a public API route. It is served only by the dedicated metrics
listener (default METRICS_ADDR=0.0.0.0:9090); the API listener returns 404 for
/metrics. The metrics listener is private only when deployment networking keeps it
internal.
Request assets are sent base64 under the names declared in request_assets. Any other
name is rejected with 400, so a caller cannot introduce arbitrary Typst file names.
jq -n --arg logo "$(base64 < assets/testdata/logo.png | tr -d '\n')" '
{
data: {number: "INV-001", customer: "Ada Lovelace", amount: "1,240.00"},
assets: {"logo.png": $logo}
}
' > invoice-render.json
curl --fail-with-body -X POST "$base/v1/templates/invoice/versions/v1/render" \
-H 'content-type: application/json' \
--data-binary @invoice-render.json \
--output invoice.pdf
The default response is application/pdf. assets may be omitted entirely for a
template that declares no request assets. To persist a result instead of returning
bytes, enable ALLOW_PERSISTED_RESULTS and send {"output":{"persist":true}}; the
response is then a JSON storage key and SHA-256 checksum.
Status codes
| Code | Meaning |
|---|---|
400 |
Invalid input or undeclared request asset |
401 |
Missing or invalid admin token |
404 |
Unknown template version or ID |
409 template version already exists or template is unknown |
Existing version or absent template ID |
409 template version lifecycle state conflicts with requested transition |
Retire or unretire requested from the wrong state |
410 |
Retired version |
413 |
Request data, an object, or a worker IPC frame exceeds a configured limit |
422 |
Request JSON extraction, JSON Schema validation, or Typst compilation failed. Telemetry uses bad_request, schema, or render, respectively |
502 |
PostgreSQL or object storage is unavailable or invalid |
503 active template version capacity exceeded |
Publication or unretirement exceeds an active-version limit; retire an active version or raise that limit |
503 template catalog load capacity exceeded or timed out |
Cold-load slots are busy or a catalog read exceeded its deadline; retry the render |
503 catalog retained-version byte capacity exceeded or incomplete |
Retained bytes are full or a legacy row needs a cold verification; raise the retained-byte limit or render that legacy version, never retire for this response |
504 |
Rendering exceeded its deadline |