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