Templates
API v1 · stablestatus
Docs / Control plane
Templates
List and read templates, and every version each one has, newest first. Publishing stays in the panel, or a push to the repository you connected. The API does not mint versions.
Authentication
Requires templates:read (or templates:manage).
Keys rule — prefix, shown once, abilities never widen, and no key carries both messages:send and messages:read-content.
Endpoints
GET/v1/templates — list · ?source · ?limit · ?cursor
GET/v1/templates/{id} — detail + versions[]: every version, newest first, each with its git_sha
GET/v1/templates/by-slug/{slug} — same detail; slug is per-account
Fields
sourcepanel when the template was created in the panel, git when a sync wrote it from the connected repository.
engineOn each version. twig or liquid. The file extension decides it: liquid stays liquid, and twig, html and txt are twig. A sidecar engine that disagrees fails the sync.
is_sensitiveWhen true, the send stores no subject and seals the body. Opening a sealed body is limited to the account owner. See sealed bodies.
variables_schemaOn each version. A send that omits a name listed under required is 422, and the message names every missing one. Extra names are refused only when the schema names the allowed set, as a vars or properties map, or with additionalProperties set to false.
git_shaOn each version. The last commit that touched the file, or null when the template came from the panel or the body was not committed. A message response does not include it. GET /v1/messages/{id} returns template_version_id, and that id is versions[].id here. The message list does not include that field.
{
"data": {
"id": 1,
"slug": "welcome",
"name": "Welcome",
"description": null,
"source": "panel",
"is_sensitive": false,
"content_retention_days": null,
"current_version": {
"id": 1,
"version": 1,
"published_at": "2026-08-06T08:11:01+00:00"
},
"versions": [
{
"id": 1,
"version": 1,
"subject": "Hello {{ name }}",
"body_html": "<p>Hello {{ name }}</p>",
"body_text": "Hello {{ name }}",
"engine": "twig",
"variables_schema": {
"required": [
"name"
]
},
"git_sha": null,
"published_at": "2026-08-06T08:11:01+00:00",
"created_at": "2026-08-06T08:11:01+00:00"
}
]
}
}List
A list row stops at current_version. versions[] is on the object above.
curl
PHP
Node
Python
Ruby
{
"data": [
{
"id": 1,
"slug": "welcome",
"name": "Welcome",
"description": null,
"source": "panel",
"is_sensitive": false,
"content_retention_days": null,
"current_version": {
"id": 1,
"version": 1,
"published_at": "2026-08-06T08:11:01+00:00"
}
}
],
"next_cursor": null
}Show
GET /v1/templates/{id} returns the object above.
curl
PHP
Node
Python
Ruby
By slug
Same detail as show, keyed on the per-account slug. The body is the same object.
curl
PHP
Node
Python
Ruby
Responses & failures
403 without the ability. Unknown id or slug on this account is 404. Slugs are per-account — the same slug on another account is a different row.