Segment Filters
A segment's filter is a tree of rules. A contact is in the segment when it matches the tree. The same format is returned by Get Segment and accepted by Create Segment and Update Segment, so you can read a filter, change it, and send it back.
Groups
The root of a filter is always a group. A group combines its conditions with and (all must match) or or (any may match), and a condition is either a rule or another group.
{
"type": "group",
"operator": "and",
"conditions": [
{ "type": "channel_status", "channel": "email", "consent": "subscribed" },
{
"type": "group",
"operator": "or",
"conditions": [
{ "type": "tag", "tag_ids": ["01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6b"] },
{ "type": "attribute", "field": "properties.plan", "operator": "equals", "value": "pro" }
]
}
]
}
A filter needs at least one rule, and may hold up to 100 rules in groups nested up to 10 levels deep.
Rules
type | Matches contacts by |
|---|---|
attribute | A contact field or custom property |
list_membership | The lists they are in |
tag | The tags they carry |
engagement_count | How many messages they were sent, opened or clicked |
last_engagement | When they last received, opened or clicked a message |
message_engagement | What they did with one campaign or automation |
event | The events they triggered |
channel_status | Whether a channel can reach them |
attribute
{ "type": "attribute", "field": "email", "operator": "ends_with", "value": "@example.com" }
field | Operators |
|---|---|
email, first_name, last_name, phone_number, external_id | equals, not_equals, contains, not_contains, starts_with, ends_with, exists, does_not_exist |
source | equals, not_equals. value is one of import, manual, transactional, api, event. |
created_at | equals, not_equals, greater_than, greater_than_or_equal, less_than, less_than_or_equal, date_is_same_month_day. value is a date (YYYY-MM-DD) or an ISO 8601 timestamp. |
properties.<key> | Any operator above. The key must be one of your organization's properties. greater_than and the other ordering operators need a number or a date. |
exists, does_not_exist and date_is_same_month_day take no value. date_is_same_month_day matches when the month and day are today's, which is how a birthday segment is built.
list_membership and tag
{ "type": "list_membership", "list_ids": ["01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"] }
{ "type": "tag", "tag_ids": ["01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6b"], "negate": true }
Matches contacts in any of the lists, or carrying any of the tags. Set negate to true for contacts in none of them. An ID that does not exist matches nobody.
engagement_count
{
"type": "engagement_count",
"channel": "email",
"action": "opened",
"operator": "greater_than_or_equal",
"value": 3,
"time_window": { "value": 90, "unit": "days" }
}
| Field | Values |
|---|---|
channel | email, sms, push |
action | email: sent, delivered, opened, clicked · sms: sent, delivered, clicked · push: sent, opened, clicked |
operator | equals, not_equals, greater_than, greater_than_or_equal, less_than, less_than_or_equal |
value | A whole number, 0 or more |
time_window | Optional. { "value": 1–365, "unit": "days" | "weeks" | "months" }. Without it, the count covers all time. |
last_engagement
{ "type": "last_engagement", "channel": "sms", "action": "clicked", "operator": "greater_than", "value": "2026-01-01" }
| Field | Values |
|---|---|
channel | email, sms, whatsapp, push, in_app |
action | sent, delivered, opened, clicked. SMS has no opened; in-app has only opened and clicked. |
operator | equals, not_equals, greater_than, greater_than_or_equal, less_than, less_than_or_equal, exists, does_not_exist |
value | A date (YYYY-MM-DD) or an ISO 8601 timestamp. Omit it for exists and does_not_exist. |
does_not_exist matches contacts who never did the action, for example everyone who has never opened an email.
message_engagement
{
"type": "message_engagement",
"target_type": "campaign",
"target_id": "01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6c",
"channel": "email",
"action": "clicked"
}
| Field | Values |
|---|---|
target_type | campaign, automation, automation_step |
target_id | The ID of that campaign, automation or step |
channel | Optional. email, sms, whatsapp, push, in_app |
action | sent, delivered, opened, clicked |
negate | Optional. true matches contacts who did not do the action. |
event
{
"type": "event",
"event_name": "order_placed",
"count": { "operator": "at_least", "value": 2 },
"time_window": { "value": 30, "unit": "days" },
"where": [
{ "field": "total", "operator": "greater_than", "value": 500, "value_type": "number" }
]
}
| Field | Values |
|---|---|
event_name | The name of one of your events |
count | Optional. { "operator": "at_least" | "at_most" | "exactly", "value": n }. Without it, the rule matches contacts who triggered the event at least once. |
time_window | Optional, as in engagement_count |
where | Optional. Up to 10 conditions on the event's data, all of which must hold. |
negate | Optional. true matches contacts the rule would otherwise leave out. |
Each where condition names a field declared on the event's schema, an operator, a value and a value_type:
value_type | Operators |
|---|---|
string | equals, not_equals, contains, not_contains, starts_with, ends_with, exists, does_not_exist |
number, date | equals, not_equals, greater_than, greater_than_or_equal, less_than, less_than_or_equal, exists, does_not_exist |
boolean | equals, not_equals, exists, does_not_exist |
A rule with where conditions reads the stored events, so its time_window cannot reach back further than the event's retention period.
channel_status
{ "type": "channel_status", "channel": "email", "consent": "subscribed", "deliverability": "deliverable" }
| Field | Values |
|---|---|
channel | email, sms, whatsapp, push |
consent | subscribed, unsubscribed |
deliverability | deliverable, undeliverable |
Set consent, deliverability, or both.
Common segments
Ready-made filters for audiences most teams need. Each is a complete filter: send it as the filter of Create Segment, replacing the IDs, event names and property keys with your own.
Engaged email subscribers
Opened at least one email in the last 30 days and still subscribed to email.
{
"type": "group",
"operator": "and",
"conditions": [
{
"type": "engagement_count",
"channel": "email",
"action": "opened",
"operator": "greater_than_or_equal",
"value": 1,
"time_window": { "value": 30, "unit": "days" }
},
{ "type": "channel_status", "channel": "email", "consent": "subscribed" }
]
}
Lapsed customers
Have ordered before, but not in the last 90 days. The second rule uses negate to match contacts with no order in the window.
{
"type": "group",
"operator": "and",
"conditions": [
{ "type": "event", "event_name": "order_placed" },
{
"type": "event",
"event_name": "order_placed",
"time_window": { "value": 90, "unit": "days" },
"negate": true
}
]
}
High-value customers
Placed two or more orders over 500 in the last 6 months. where reads the total field declared on the event.
{
"type": "group",
"operator": "and",
"conditions": [
{
"type": "event",
"event_name": "order_placed",
"count": { "operator": "at_least", "value": 2 },
"time_window": { "value": 6, "unit": "months" },
"where": [
{ "field": "total", "operator": "greater_than", "value": 500, "value_type": "number" }
]
}
]
}
Birthday today
Contacts whose birthday property falls on today's month and day. The segment's members change every day, so it suits a daily campaign.
{
"type": "group",
"operator": "and",
"conditions": [
{ "type": "attribute", "field": "properties.birthday", "operator": "date_is_same_month_day" }
]
}
New contacts created through the API
Created on or after a date, with the API as their source.
{
"type": "group",
"operator": "and",
"conditions": [
{
"type": "attribute",
"field": "created_at",
"operator": "greater_than_or_equal",
"value": "2026-03-01"
},
{ "type": "attribute", "field": "source", "operator": "equals", "value": "api" }
]
}
Reachable by SMS, never clicked
Subscribed to SMS with a deliverable number, and no SMS click on record.
{
"type": "group",
"operator": "and",
"conditions": [
{
"type": "channel_status",
"channel": "sms",
"consent": "subscribed",
"deliverability": "deliverable"
},
{
"type": "last_engagement",
"channel": "sms",
"action": "clicked",
"operator": "does_not_exist"
}
]
}
Clicked a campaign, not yet in a list
Clicked one email campaign and are in none of the given lists: a follow-up audience that leaves out people already handled.
{
"type": "group",
"operator": "and",
"conditions": [
{
"type": "message_engagement",
"target_type": "campaign",
"target_id": "01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6c",
"channel": "email",
"action": "clicked"
},
{
"type": "list_membership",
"list_ids": ["01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"],
"negate": true
}
]
}
Either of two groups
Subscribed to email, and either tagged VIP or on the pro plan. The inner group is an or inside the outer and.
{
"type": "group",
"operator": "and",
"conditions": [
{ "type": "channel_status", "channel": "email", "consent": "subscribed" },
{
"type": "group",
"operator": "or",
"conditions": [
{ "type": "tag", "tag_ids": ["01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6b"] },
{ "type": "attribute", "field": "properties.plan", "operator": "equals", "value": "pro" }
]
}
]
}
The event examples assume an event named order_placed with a numeric total field, and the property examples assume birthday and plan properties. A filter that names an event field or property your organization has not defined is refused.
Validation errors
A filter that breaks a rule is refused with 422 validation_error. The message gives the path of each problem inside the tree:
{
"status_code": 422,
"name": "validation_error",
"message": "Invalid filter: conditions.1.operator: \"created_at\" does not accept the operator \"contains\""
}