The expression grammar
An expression is one condition or a group of them. Groups are{ all: [...] }, { any: [...] } and { not: … }, each needing at least one child. Nesting goes at most 8 levels deep with at most 50 conditions in total.
Attribute comparisons
Comparators areeq, neq, gt, gte, lt, lte, in (up to 100 values), contains (a case-insensitive substring) and exists. Nested keys use dots, as in attributes.address.city, and ref: "externalId" compares the external id itself.
The value’s type picks the reading. Numbers compare numerically, booleans as booleans, everything else as text.
eq, gt and the other ordered comparators require the key to be present. neq is the exact complement of eq, so a missing key counts as not equal. eq: null and exists: false both mean the key is missing.Events, activity and durations
Event counts takeeq, gt, gte, lt and lte on a non-negative integer. eq: 0, lt: n and lte: n all include subscribers who never tracked the event at all, which is the same audience never describes without a count.
Activity means the last time the subscriber was seen on a device. iOS, Android and web events count; events you send from your server do not. Subscribers never seen on a device match neither within nor olderThan.
Durations are written <n>m, <n>h or <n>d, so 15m, 12h and 30d. Event names follow the tracking rules, and system events such as $app.opened count like any other.
Create and version a segment
1
Create it
201 with the segment at version 1. new and preview are reserved slugs and answer 400 slug_reserved; a slug already in use is a 409 slug_taken.2
Edit it
PATCH /v1/segments/active-pro takes name, description (null clears it) and expression. A changed expression creates the next version. An identical one does not, so a save with no edit is free.3
Read it back
GET /v1/segments/active-pro returns the segment with its current version: { id, slug, name, description, version: { id, number, expression, createdAt }, createdAt, updatedAt }. GET /v1/segments lists every segment of the tenant the same way.DELETE /v1/segments/:slug soft-deletes it and frees the slug. Messages already sent keep the version they used.
Preview before you save
POST /v1/segments/preview evaluates an expression without saving anything and answers { count, sample }, the number of subscribers matching right now and the first 20 of them as subscriber list items. It needs only segments:read, so it is the dry run to make before a large send.
GET /v1/segments/:slug/members pages the full membership by subscriber id with limit up to 100 and cursor from nextCursor. The first page carries total.
Send to a segment
targets carry { segment, segmentVersion }.
segment cannot be combined with to (400 targets_conflict). It can be combined with topic, which then filters members by their topic preference exactly as a topic send does. An unknown or deleted segment on a send is a 404.
Inline expressions
When an audience is used once, skip the segment and pass the same grammar inline aswhere. It is evaluated for this send only and stored verbatim on the message as targets.where, so the audience stays explainable afterwards.
invalid_expression whose param points at the failing node, for example expression.all[1] or where.all[1].
Next
Messages
Every targeting option, content field and the delivery guarantees behind them.
Workflows
Run the same conditions per subscriber, with waits and branches.