Generate documents programmatically with a keyed REST API. Full reference: Swagger UI.
Settings → API Keys → Create. Copy the plk_live_… key when it’s shown — it isn’t displayed again. Keyed usage is billed to your plan.
What a key can do. A key is scoped to /v1 and nothing else: generate documents, list your approved templates, and export an approved template’s definition together with the images that template uses. It cannot write anything — no editing, creating or deleting templates, assets, usage or billing — and it cannot create or revoke keys; those need a signed-in session. So a key sitting in a CI secret or a Zapier connection is a generation-and-read credential, not a workspace login. A leaked one can spend your generation allowance and read your approved templates and their images until you revoke it.
Send the key as a Bearer token or the x-api-key header:
Authorization: Bearer plk_live_xxx
# or
x-api-key: plk_live_xxx
POST /v1/documents with a template id and the data to bind:
curl -X POST https://api.ristmark.app/v1/documents \
-H "Authorization: Bearer plk_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "templateId": "<id>", "format": "pdf", "data": { "invoiceNo": "NW-2026-0184" } }'
Returns the rendered document bytes. format is pdf (default), html or docx. Omit data (or pass null) to render the template’s sample data — the response then carries a sample-data-used warning (see below) so you can tell a real render from a demo one. Only approved templates are generatable via the API.
GET /v1/templates lists the caller’s approved templates only — in_review (freshly migrated, unconfirmed) templates never appear here, matching the trust fence on generation itself:
curl https://api.ristmark.app/v1/templates \
-H "Authorization: Bearer plk_live_xxx"
Each item is { id, name, latestVersion? }. This route is read-only discovery — it isn’t metered.
GET /v1/templates/{id}/export returns the template itself — the reusable artifact, not a rendered
output of it:
curl -OJ https://api.ristmark.app/v1/templates/<id>/export \
-H "Authorization: Bearer plk_live_xxx"
The response is a ristmark.template.v1 envelope: the template’s canonical JSON (blocks, field
definitions, $data bindings, $t translations, width tokens, locale and currency) plus the images
and letterheads it references, base64-embedded, so the bundle stands on its own. Digests are computed
over the bytes the response carries, so it verifies itself.
Two lists tell you where a bundle is not complete, rather than leaving you to find out:
unresolvedAssetIds names an asset this workspace could not produce bytes for, and
dataBoundImageSrc names any image whose src is a {{ }} binding — that picture is chosen by the
data you generate with, so no export can enumerate it. The bundle carries whichever of those the
template’s own sample data resolves to; bring the rest yourself. Both are empty for the ordinary
template.
?version=<n> exports a published revision; omit it for the current working copy — the same meaning
version has on POST /v1/documents. Approved templates only, same trust fence as generation.
If a template’s assets are too large to embed in one response, the route refuses with 413 and names
?assets=omit, which returns the same template JSON plus an asset manifest (ids, MIME types, sizes,
SHA-256 digests) without the bytes.
This route is not metered, so an export does not consume your generation allowance.
There is no import endpoint yet: re-creating an exported template in a Ristmark workspace today means
posting its template object to the signed-in workspace API and re-uploading its assets.
POST /v1/documents/batch with up to 25 items, each { templateId, data, format } — each item is metered as one generation, minimum (a very long generated item — over 20 rendered pages — counts one extra generation per additional 20 pages; see Pricing & limits). A response looks like:
{
"requested": 2,
"succeeded": 2,
"results": [
{ "index": 0, "status": "ok", "format": "pdf", "document": "<base64>" },
{ "index": 1, "status": "ok", "format": "pdf", "document": "<base64>" }
]
}
Partial success is the contract: one bad item never fails the others — a failed item comes back as { "status": "error", "error": { "code", "message" } } instead of stopping the batch. Batch warnings ride per-item JSON (each result’s warnings array), not the X-Ristmark-Render-Warnings header — a single HTTP response covers up to 25 items, so there’s no one header to carry a per-item signal on.
Have many rows of tabular data instead of a hand-built JSON array? Generate from a spreadsheet covers the CSV/Excel upload path (up to 200 rows) instead.
402 — plan inactive or over cap. 429 — rate limited. 404 — template not found / not approved. 422 — the template produced no visible pages for the given data (every section hidden by its visibility condition). 400 — invalid request (bad UUID, bad format, empty/over-cap batch, unrecognised assets value). 413 — an export’s assets are too large to embed; retry with ?assets=omit.X-Ristmark-Render-Warnings response header — the document still returns; it’s a signal to look closer, not a failure.sample-data-used rides the same warning channel when data was omitted/null and the render fell back to the template’s sample data — worth checking for if you expected a real render.