Snippets
Snippets are values that any template, block or subject line can use, such as the next webinar’s title, date and registration link. An email reads them with Liquid when it sends:
{{ snippets.webinar.title }}
The root is always snippets, plural. Changing a snippet therefore changes
every email that hasn’t sent yet. That includes draft and scheduled broadcasts
and library emails already pinned to a sequence step, with no re-save and no
new version.
A set (webinar) defines the fields (title, date, url, …) and how its
snippets take turns. Each snippet in a set is an entry in this API: one
row of values. One entry is current at any moment, or none is:
rotation | Which entry is current |
|---|---|
single | Its one entry. Change it by hand. |
next_upcoming | The entry whose order_field date comes next. It rolls off when its date passes, and the next one takes over. Use it for webinars and events. |
scheduled | The latest entry whose starts_at has passed. It shows until the next one starts. Use it for a featured guide or a monthly offer. |
Values lock when a send starts. A broadcast reads them once, when it starts sending, and every recipient gets those values, including every A/B variant and a split test’s winner. Each later day of a spread send locks its own values when that day goes out. A sequence email locks them when that email sends. View in browser shows the values the recipient received.
Required scopes#
Snippets ride the template scopes, as blocks do.
| Endpoint | Scope |
|---|---|
GET /api/snippets | templates:read |
GET /api/snippets/{set} | templates:read |
GET /api/snippets/resolve | templates:read |
POST /api/snippets | templates:write |
PATCH /api/snippets/{set} | templates:write |
DELETE /api/snippets/{set} | templates:write |
POST /api/snippets/{set}/entries | templates:write |
PUT /api/snippets/{set}/entries | templates:write |
PATCH /api/snippets/{set}/entries/{id} | templates:write |
DELETE /api/snippets/{set}/entries/{id} | templates:write |
{set} is the set’s UUID or its key.
Fields and values#
Each field has a key (its Liquid name), a label and a type:
type | Value you send | What Liquid reads |
|---|---|---|
text, multiline_text | a string | the string, HTML-escaped in the body |
rich_text | an HTML string | the HTML, unescaped in the body |
url, image | an http(s) URL | the URL |
number | a number | the number |
boolean | true or false | the boolean |
date | YYYY-MM-DD | YYYY-MM-DD |
datetime | an ISO-8601 instant, or YYYY-MM-DDTHH:MM in the account’s time zone | the instant written in the account’s UTC offset, e.g. 2026-10-22T13:00:00-04:00 |
A datetime prints in the account’s time zone with the date filter:
{{ snippets.webinar.date | date: "%a %b %-d, %-I:%M %p" }} reads
Thu Oct 22, 1:00 PM. Keys are lowercase letters, digits and _, starting
with a letter. A field you leave out of an entry renders empty.
Limits: 50 sets per account, 30 fields per set, 200 entries per set, 10 KB per value.
Create a set#
POST /api/snippets
{
"name": "Webinar",
"key": "webinar",
"rotation": "next_upcoming",
"order_field": "date",
"fields": [
{ "key": "title", "label": "Title", "type": "text" },
{ "key": "description", "label": "Description", "type": "rich_text" },
{ "key": "date", "label": "Webinar date", "type": "datetime" },
{ "key": "url", "label": "Registration link", "type": "url" }
]
}
Returns 201 with the set. A key already in use returns 409 KEY_TAKEN.
List and read sets#
GET /api/snippets
Each set comes with current (the entry showing now, or null),
upcoming_count, and runs_out_at: when the last lined-up entry of a
next_upcoming set rolls off, after which nothing is current.
GET /api/snippets/{set}
The set with every entry, in queue order. Each entry has values,
shows_from / shows_until (its window), state (past, current or
upcoming) and updated_by (the user or API key that last changed it).
Change or delete a set#
PATCH /api/snippets/{set}
Send any of name, fields, rotation, order_field, key. Once the set has
entries, its shape is locked except for names: you can rename it, relabel
fields and add new ones, but changing the key, the rotation, or an existing
field’s key or type returns 409 SET_LOCKED.
DELETE /api/snippets/{set}?force=true
Refused with 409 HAS_ENTRIES while the set has entries, unless
force=true. Emails that read a deleted set render its fields empty.
Add an entry#
POST /api/snippets/{set}/entries
{
"values": {
"title": "Raising prices without losing customers",
"date": "2026-10-22T13:00",
"url": "https://example.com/webinar/raising-prices"
}
}
A next_upcoming entry is placed by its order_field value, which is
required. A scheduled entry needs starts_at (an ISO instant, or
YYYY-MM-DDTHH:MM in the account’s time zone). Two entries can’t share a place
in the queue: a second entry at the same date or start returns
409 SLOT_TAKEN. A single set holds one entry: a second returns
409 ENTRY_EXISTS. Returns 201 with the entry.
Upsert an entry#
PUT /api/snippets/{set}/entries
Same body as adding one, plus an optional match_field. Use it from a sync job,
such as a weekly task that loads the next webinar: running it twice doesn’t
make a duplicate. Returns 201 when it added an entry and 200 when it
replaced one.
- With
match_field, the key of a text or URL field such as a registration link or your own id, it replaces the entry whose value in that field matches. If the webinar was rescheduled, the entry moves to its new date instead of being added again. Prefer this for anything that can change its date. - Without it, it replaces the entry at the same place in the queue: the same
date for
next_upcoming, the samestarts_atforscheduled, or the set’s one entry forsingle.
{
"match_field": "url",
"values": {
"title": "Value metrics that scale",
"date": "2026-12-03T13:00",
"url": "https://example.com/webinar/value-metrics"
}
}
Change or delete an entry#
PATCH /api/snippets/{set}/entries/{id}
values replaces the entry’s values as a whole: a field you leave out is
cleared. starts_at moves a scheduled entry.
DELETE /api/snippets/{set}/entries/{id}
Emails already sent keep the values they were sent with.
Resolve at a moment#
GET /api/snippets/resolve?at=2026-12-10T15:00:00Z
What an email sent at at (default: now) would read as snippets:
{
"at": "2026-12-10T15:00:00.000Z",
"values": {
"webinar": {
"title": "Discounting without damage",
"date": "2027-01-14T13:00:00-05:00",
"url": "https://example.com/webinar/discounting"
},
"resource": null
}
}
In an email#
Wrap a section in a check, so it disappears when nothing is current:
{% if snippets.webinar %}
<h2>{{ snippets.webinar.title }}</h2>
<a href="{{ snippets.webinar.url }}">Save my seat</a>
{% endif %}
In MJML, put the check in <mj-raw> tags around the section. MJML discards
bare text between its elements, so a bare {% if %} there would be dropped
and the section would always render:
<mj-raw>{% if snippets.webinar %}</mj-raw>
<mj-section>…</mj-section>
<mj-raw>{% endif %}</mj-raw>
Links in snippets are click-tracked like any other link.
Errors#
| Code | Status | Meaning |
|---|---|---|
INVALID_INPUT | 400 | A field definition or value failed validation. The message names it. |
NOT_FOUND | 404 | No set (or entry) with that id or key. |
KEY_TAKEN | 409 | Another set already uses this key. |
SET_LOCKED | 409 | The change would strand stored entries or move the queue. |
ENTRY_EXISTS | 409 | A single set already has its entry. Change it instead. |
SLOT_TAKEN | 409 | Another entry already has this date (or start). Change it, or edit that entry. |
HAS_ENTRIES | 409 | Delete with force=true, or delete the entries first. |
LIMIT_REACHED | 400 | A per-account or per-set limit was hit. |