Skip to content

Templates

HTML/CSS render templates for the render_template op: create, version, import/export

7 endpoints under /api/v1/templates. What every call shares is on the REST overview, and what they are for — with the calls for every SDK, the CLI and MCP — on Templates. * marks a required field.

List templates

GET /api/v1/templates

Paged — page and perPage, or cursor, in; meta out

Built-in templates first, then the caller's own, most recently updated first, a page at a time. Each row is the current version without its html and css — read one with GET /api/v1/templates/{id}, which is also the document POST /api/v1/templates/import takes back.

FILTER: omit for both, builtin for the shipped ones only, user for your own only.

Parameters

NameInTypeDescription
filterquerystring'builtin' or 'user'; omit for bothone of builtin · user

Returns 200 — data is TemplateSummary[]

One page of templates; meta says whether there is another

Errors

StatusCode and when
400
  • invalid_param — filter is neither builtin nor user
  • invalid_param on cursor — not a cursor this listing issued, or sent with page

Create a template

POST /api/v1/templates

Accepts an Idempotency-Key

Creates version 1 and answers 201 with it. name, html, width and height are required. html ≤ 256 KB and css ≤ 128 KB (UTF-8 bytes), width / height 1–4096 px; anything outside is 400 invalid_param naming the field. variables defaults to the {{ … }} names found in the html and css. Each account may own at most 200 templates (422 resource_limit_exceeded).

Rendering is POST /api/v1/jobs {"op": "render_template", "templateId": …, "items": [...]}, or one row now with POST /api/v1/images/render.

Request body — TemplateData

Returns 201 — data is TemplateData

The template, at version 1

Errors

StatusCode and when
400
  • invalid_param on name — missing, or over 200 characters
  • invalid_param on html / css — html missing, or either over its limit
  • invalid_param on width / height — missing, or outside 1–4096
  • invalid_param on variables — more than 100, or a name that is not an identifier (letters, digits, _ and .)
  • invalid_param — the slug you sent is malformed, starts with builtin-, or is already one of yours
422

resource_limit_exceeded — the account already owns 200 templates

Import templates

POST /api/v1/templates/import

Accepts an Idempotency-Key

Takes a list of template documents — what GET /api/v1/templates/{id} returns, one per template — and creates each as a new template at version 1: history and slug are not carried over (the slug is made from the name, with a numeric suffix when it is taken). The list rows of GET /api/v1/templates carry no html, so they are not an export.

Each entry is checked like a create; one that fails is reported in errors and the rest still land, so a 201 can carry refusals — read errors. The whole import is refused (422 resource_limit_exceeded) if it would take the account past 200 templates.

Request body — TemplateData[]

Returns 201 — data is BatchImportResultDTOTemplateData

What landed, and one line per entry that did not

Errors

StatusCode and when
400

invalid_param — the body is not a non-empty list

422

resource_limit_exceeded — the entries would take the account past 200 templates; nothing was imported

Get a template (or one frozen version of it)

GET /api/v1/templates/{id}

{id} is a template id (or your own slug) for the current version, or {id}@{version} for one specific version — the same spelling render_template accepts as templateId, so what a job rendered with can always be read back. Built-ins have exactly one version.

An earlier version comes back as a document whose id is {id}@{version}, without slug, description or updatedAt — those belong to the template, not to a version of it.

Parameters

NameInTypeDescription
id (required)pathstringTemplate id, slug, or id@version

Returns 200 — data is TemplateData

The template document

Errors

StatusCode and when
400

invalid_param on templateId — the @version is not a positive number

404

not_found on templateId — no such template or version, or it is not yours

Update a template (new version)

PUT /api/v1/templates/{id}

Accepts an Idempotency-Key

Send only what changes: the body is merged over the current version, and a field it leaves out keeps its value ({"width": 1080, "height": 1080} is a whole update); "" empties css or description. variables, when not sent, stay as they are unless html or css changes, and are then derived from the new markup. Same limits as create. Never edits in place: the template moves to version + 1 — every PUT does, a rename included — and the previous bytes stay readable as {id}@{version}, so a job or automation that pinned a version keeps rendering exactly that. Built-in ids are 403; to start from one, create your own with its html.

Parameters

NameInTypeDescription
id (required)pathstringTemplate id or your own slug

Request body — TemplateData

Returns 200 — data is TemplateData

The template, at its new version

Errors

StatusCode and when
400

invalid_param naming the field — the merged document fails a create's check (see POST /api/v1/templates), or a new slug is malformed, reserved or taken

403

forbidden — a built-in template

404

not_found on templateId — no such template, or it is not yours (a path carrying @version names none: a version cannot be edited)

Delete a template

DELETE /api/v1/templates/{id}

Accepts an Idempotency-Key

Deletes the template and every version of it. A render job already rendering keeps the bytes it was handed; one still queued behind the account's other jobs fails unstarted (invalid_state). Built-in ids are 403.

Parameters

NameInTypeDescription
id (required)pathstringTemplate id or your own slug

Returns 204 — no body

Deleted; no body

Errors

StatusCode and when
403

forbidden — a built-in template

404

not_found on templateId — no such template, or it is not yours

List a template's versions

GET /api/v1/templates/{id}/versions

Every version ever saved, newest first, each as a full document — html and css included — whose id is {id}@{version} and which has no slug, description or updatedAt. Not paged: a template's history is one answer. A built-in answers with its one current document.

Parameters

NameInTypeDescription
id (required)pathstringTemplate id or your own slug — not id@version

Returns 200 — data is TemplateData[]

Every version, newest first

Errors

StatusCode and when
404

not_found on templateId — no such template, or it is not yours

Objects

Each described once; a linked type is another object on this page.

BatchImportResultDTOTemplateData

What an import did: the entries that landed, and one line for each that did not

FieldTypeDescription
dataTemplateData[]
The created resources, as saved, in the order sent
errorsstring[]
One line per refused entry, naming its index and why (Preset at index 2 'Hero': …). Absent when every entry landed.
importedCountinteger (int32)
How many entries were created — the length of data

TemplateData

HTML/CSS render template with {{ var }} placeholders; the input of the render_template op

FieldTypeDescription
builtInboolean
Whether this is a built-in template (read-only; PUT and DELETE are 403)
createdAtstring (date-time)
When the template — or, read as id@version, that version — was created
cssstring
Stylesheet (≤ 128 KB as UTF-8). Like the html, it loads nothing but data: URIs and this product's own asset host; any other @import or url() is blocked at render time.
descriptionstring
Free text for people; not rendered. "" on a PUT empties it.
heightinteger (int32)
Viewport / output height in px (required on create; 1–4096)e.g. 1080
htmlstring
Body HTML (required on create; ≤ 256 KB as UTF-8). Scripts never run; every request it makes is blocked at render time except data: URIs and this product's own asset host.
idstring
Template id (tpl_…; a built-in's is builtin-template-…). A version read by id@version carries that spelling here.
namestring
Display name (required on create; at most 200 characters)e.g. "Quote card"
slugstring
URL-safe handle, unique per account: lowercase letters, digits and hyphens. Generated from the name when omitted; one you send must be free. builtin- is reserved.
updatedAtstring (date-time)
When the current version was saved; absent on a version read as id@version
variablesstring[]
Placeholder names (at most 100, each letters, digits, _ and .). Derived from the html/css when omitted. A render fills {{ name }} HTML-escaped and {{{ name }}} raw; a name the row does not carry renders as nothing.
versioninteger (int32)
Current version; every PUT increments it and keeps the old one readable as id@version
widthinteger (int32)
Viewport / output width in px (required on create; 1–4096)e.g. 1080

TemplateSummary

A template as it is listed: everything but its html and css, which GET /api/v1/templates/{id} returns

FieldTypeDescription
builtInboolean
Whether this is a built-in template (read-only)
createdAtstring (date-time)
When the template was created
descriptionstring
Free text for people
heightinteger (int32)
Output height in px
idstring
Template id (tpl_…, or builtin-template-…); id@version addresses one frozen version
namestring
Display name
slugstring
URL-safe slug
updatedAtstring (date-time)
When its current version was saved — the order your own rows are listed in
variablesstring[]
Placeholder names a render fills in
versioninteger (int32)
Current version
widthinteger (int32)
Output width in px