Build an audience from a filter. Creating a segment sends nothing — it defines who a later bulk send would reach, and returns the count and a sample so you can check the filter did what you meant before anyone approves anything.
| Method | Path | Description |
|---|---|---|
POST | /api/v1/segments | Create a smart segment from a filter. Returns its resolved audience. |
Requires the segments:write scope, which is never granted to an OAuth-connected agent and never inherited by default — see Scopes. The segment is a smart segment: membership is a live query, so it keeps up as contacts change. A bulk send freezes the membership it was reviewed against, so approving one later cannot mail a set nobody looked at.
There is no list, update or delete endpoint. Segments you create appear in the app under Audience → Segments and are managed there.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | required | A human-readable name. Whoever approves a send to this segment sees it, so make it describe the filter. |
rules | object | required | The filter tree — see below. Must narrow the audience; an empty filter is rejected. |
| Field | Type | Description |
|---|---|---|
id | string (uuid) | Pass this as segment_id to POST /send/bulk. |
name | string | As supplied. |
recipient_count | number | Contacts the filter matches that could actually be mailed — subscribed and not suppressed. It is deliberately not the raw membership count. |
sample | object[] | Up to five matched contacts, with the local part of each address masked (a***@acme.com). Enough to confirm the filter, not enough to read out the list. The person approving a send sees the full addresses. |
rules is a tree. A group has a match of "all" (AND) or "any" (OR) and a list of conditions; each condition is either a leaf { field, operator, value } or another group. Groups nest up to three deep.
| Field | Matches on |
|---|---|
status | subscribed, unsubscribed, bounced. |
first_name / last_name / email | Text matching on the contact's own fields. |
created_at | When the contact was added — before / after / in the last N days. |
property:<key> | Any custom property on your contacts, e.g. property:city. |
engagement:opened / clicked / sent | Behaviour, e.g. opened anything in the last 30 days. Reads open data, so it means less on contacts whose sends had open tracking off. |
verification:state | What list verification concluded about the address: valid, risky, invalid, or unverified. "Verification result is valid" is how you build a segment of confirmed-good addresses. |
Operators depend on the field: is, is_not, contains, does_not_contain, is_empty, is_not_empty, the comparisons eq/neq/gt/lt/gte/lte, and for dates and engagement before, after, in_last_days and ever. A condition the compiler can't make sense of matches nobody rather than everybody.
A filter with no real conditions — an empty conditions array, or groups containing only empty groups — is rejected with 400 invalid_field on rules. Such a filter is vacuously true, so it would quietly resolve to your entire audience: the difference between a segment of 400 people and one of 40,000 would be a missing line of JSON. Reaching everyone is a deliberate choice you make in the app, not something a malformed filter should be able to do by accident.
| Status | code | When |
|---|---|---|
400 | missing_field | name or rules absent. |
400 | invalid_field | rules is not a filter object, uses an unknown field or operator, nests too deep, or matches everyone (see above). Also when name is not a string. |
401 | unauthorized | Missing or invalid API key. |
403 | insufficient_scope | The key lacks segments:write. |
429 | rate_limited | Per-key rate limit exceeded. |
500 | internal_error | The segment could not be created or its membership could not be computed. Nothing partial is left behind. |
See Error codes for the envelope and the full list.