API reference

Templates

Reusable messages with per-recipient variables, kept in sync over the API.

Templates

A template is a reusable message with variables that fill themselves in per recipient: Salaam {name}! Your order is ready. becomes a personal message for each contact at send time. Templates written here are the same ones your team sees in the dashboard's library — an integration can keep them in sync with an external CMS, and a campaign can send one to a whole audience (see Campaigns).

Scopes: templates:read for reading and previewing, templates:write for creating, editing and deleting.

Variables

Write a variable as a name in single curly braces: {name}. Available everywhere: {name}, {first_name}, {phone}. Beyond those, every custom field you have defined on contacts is available under its key, and a contact's metadata keys work the same way. Names are case-insensitive.

A variable the contact cannot fill is left as written, not blanked — a visible {name} in a message is caught by whoever previews it; an invisible gap is not. The preview endpoint lists unfilled variables so you can catch them before sending.

Create a template

POST /api/v1/templates
{
  "name": "Order ready",
  "content": "Salaam {name}! Your order is ready for pickup.",
  "category": "notifications",
  "shortcut": "/ready",
  "media_id": null
}
Field Required Notes
name yes Up to 160 characters
content yes The message body, up to 4,096 characters, variables in {braces}
category no Free-form label for organising the library, up to 60 characters
shortcut no A /slash shortcut your team can type in the inbox composer
media_id no Id of a retained media object to attach (see Media)

Returns 201 with the template. id is the identifier every other call uses. The response also lists the variables found in the body, so you can validate what a sync just uploaded:

{
  "data": {
    "id": "9b2f…",
    "name": "Order ready",
    "content": "Salaam {name}! Your order is ready for pickup.",
    "category": "notifications",
    "shortcut": "/ready",
    "variables": ["name"],
    "attachment": null,
    "usage_count": 0,
    "last_used_at": null
  }
}

Send an Idempotency-Key header on creation, as with every write (see Errors and idempotency).

List and read

GET /api/v1/templates
GET /api/v1/templates?search=order
GET /api/v1/templates/{id}

The list is paginated (page, per_page up to 100) and search matches the name. Each item carries usage_count and last_used_at, so a sync can tell which templates are actually earning their place.

Preview — what will this contact actually receive?

GET /api/v1/templates/{id}/preview
GET /api/v1/templates/{id}/preview?contact_id={contact_id}

Renders the stored body with a real contact's values — the named one, or the workspace's first contact if you name none. This uses the same renderer a real send uses, so what the preview shows is what dispatch produces:

{
  "data": {
    "contact": { "id": "d1c4…", "name": "Ayesha Khan", "phone": "+923001234567" },
    "body": "Salaam Ayesha Khan! Your order is ready for pickup.",
    "missing": []
  }
}

missing names every variable that contact could not fill. A non-empty missing on your typical contacts means the message will go out with literal braces in it — fix the template or the data before a campaign does it at scale.

Update and delete

PUT /api/v1/templates/{id}
DELETE /api/v1/templates/{id}

PUT takes the same fields as creation; omitted fields keep their value. DELETE returns 204.

Deleting a template does not touch campaigns that already used it: a campaign resolves the template to concrete words at the moment it is scheduled, not at dispatch, precisely so a later edit or deletion cannot change what an approved campaign says.

Was this guide useful? Report an issue