Lists & segments
Static lists, dynamic segments, and their membership.
The Segment object 14 fields
Back-compat alias for the active (member_count) value, defaulting to 0. Present on the single-segment GET response.
The rule tree for a dynamic segment; null for a static list. A recursive structure of { logic, conditions[] } where a condition is a leaf filter or a nested group — see the Lists & segments guide. Kept permissive here on purpose: it is a DSL, not a fixed shape.
Session responses only — replaced by segment_key for API-key callers.
Session responses only — the stable public key, surfaced as segment_key to API-key callers.
Cached SENDABLE member count — matches that would actually receive a send (excludes unsubscribed, bounced, complained, DNC, suppressed). Null until first evaluated. Refresh with POST /{id}/refresh-count.
When the cached counts were last computed.
Cached raw match count. member_count_total − member_count is the unsendable remainder.
Display label. Also the seed for key on creation; renaming does not change the key.
API-key responses. The stable public identifier — use it in broadcast include_segment_keys / exclude_segment_keys and as the {id} path parameter, rather than storing the raw UUID.
Free-text labels.
dynamic is rule-based (has a definition); static is an explicit membership list. Fixed at creation.
dynamic, staticThe Contact object 62 fields
hard, soft, nullCategory-level opt-out state, keyed by email category ID. Source of truth for categorised email types.
Values for fields declared via the custom-fields API. Writing an undeclared key returns UNKNOWN_FIELD.
healthy, watch, at_risk, suppressedThe match key for upserts. Unique per tenant.
Your own record key. Keep it populated so a re-sync can reconcile.
Account-wide sendability. suppressed is set by a soft delete.
active, unsubscribed, bounced, complained, suppressedCanonical contact ID. Store this.
Free-text origin label supplied by the caller.
active, unsubscribed, bounced, complained, suppressed, nullPer-email-type opt-in state. A missing key means subscribed.
Create a dynamic segment from broadcast engagement
Builds a new dynamic segment whose rule targets one broadcast's engagement — the fast way to make a follow-up audience like "everyone who clicked the launch email".
Pick the audience with criterion:
engagers— opened OR clickedopeners/non_openers— received it and did / did not openclickers/non_clickers— received it and did / did not clickbouncers/complainers/unsubscribers— bounced, complained, or unsubscribed on itnot_received— was in the source broadcast's include audience but has no send event (the follow-up target after a holdback or partial spread). It is bounded by the source broadcast's include/exclude segments, so the source must have at least one include segment or the call fails withINVALID_CRITERION.
The result is an ordinary dynamic segment: it keeps re-evaluating, so an openers segment grows as more opens arrive. Requires segments:write (it always creates a dynamic segment, never a list).
The broadcast whose engagement defines the audience.
Which slice of the broadcast's audience to capture.
engagers, openers, non_openers, clickers, non_clickers, bouncers, complainers, unsubscribers, not_receivedName for the new segment. Its key is derived from this.
The Segment object.
The dynamic segment was created.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X POST "https://api.sonarsend.com/t/acme/api/segments/from-broadcast" \
-H "X-API-Key: $SONARSEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme onboarding",
"broadcast_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"criterion": "engagers"
}'const res = await fetch('https://api.sonarsend.com/t/acme/api/segments/from-broadcast', {
method: 'POST',
headers: {
'X-API-Key': process.env.SONARSEND_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Acme onboarding",
"broadcast_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"criterion": "engagers"
}),
});
const data = await res.json();{
"id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"key": "welcome-series",
"segment_key": "welcome-series",
"tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"name": "Acme onboarding",
"type": "dynamic"
}List segments and lists
Every segment and list in the account, in one page (no cursor — the collection is small). For an API-key caller the response is filtered by scope: a key with only lists:read sees static lists, one with only segments:read sees dynamic segments, one with both sees everything. Cached counts on each row may be stale; read a single segment or call refresh-count for a fresh number.
A page of Segment objects, under data.
The segments and lists visible to this key.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X GET "https://api.sonarsend.com/t/acme/api/segments" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/segments', {
method: 'GET',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"data": [
{
"id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"key": "welcome-series",
"segment_key": "welcome-series",
"tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"name": "Acme onboarding",
"type": "dynamic"
}
],
"next_cursor": "eyJpZCI6IjljMjE4OTVkIn0",
"has_more": true
}Create a segment or list
Creates a dynamic segment or a static list. type defaults to dynamic when omitted.
- Dynamic (
type: "dynamic") requires adefinition— omitting it fails withMISSING_DEFINITION. The definition is the recursive rule tree{ logic, conditions[] }; see the Lists & segments guide for the condition grammar. Needs thesegments:writescope. - Static (
type: "static") ignoresdefinitionand starts empty — add members withPOST /{id}/members. Needs thelists:writescope.
The route accepts either scope, then enforces the one matching the type you asked for — so a key with only lists:write gets INSUFFICIENT_SCOPE if it tries to create a dynamic segment. The key is generated from name (de-duplicated with a numeric suffix) and returned as segment_key to API-key callers.
Display name. Seeds the generated key.
Rule tree for a dynamic segment. Required for dynamic, ignored for static. A recursive { logic, conditions[] } DSL — kept permissive; see the guide.
Defaults to dynamic. Fixed once set.
dynamic, staticThe Segment object.
The segment or list was created.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X POST "https://api.sonarsend.com/t/acme/api/segments" \
-H "X-API-Key: $SONARSEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme onboarding",
"type": "dynamic",
"definition": {}
}'const res = await fetch('https://api.sonarsend.com/t/acme/api/segments', {
method: 'POST',
headers: {
'X-API-Key': process.env.SONARSEND_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Acme onboarding",
"type": "dynamic",
"definition": {}
}),
});
const data = await res.json();{
"id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"key": "welcome-series",
"segment_key": "welcome-series",
"tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"name": "Acme onboarding",
"type": "dynamic"
}Get one segment or list
Fetches a single segment or list. The {id} is the segment_key for API-key callers (the UUID for a session). The read is scope-gated by the target's type: reading a static list needs lists:read, a dynamic segment needs segments:read.
Returns cached member counts. On the first read after creation (or a migration) the counts are computed live and cached; later reads serve the cached values, so a number can lag reality — force a recompute with POST /{id}/refresh-count. count is a back-compat alias for the active (member_count) figure.
Segment key (API key) or UUID (session).
The Segment object.
The segment or list, with cached counts.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X GET "https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80', {
method: 'GET',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"key": "welcome-series",
"segment_key": "welcome-series",
"tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"name": "Acme onboarding",
"type": "dynamic"
}Update a segment or list
Renames a segment, or replaces a dynamic segment's definition. Both fields are optional; send only what you change. The write is scope-gated by the target's type (lists:write for a list, segments:write for a segment).
Replacing definition re-evaluates and re-caches the counts. A definition on a static list is stored but has no effect — lists are driven by explicit membership, not rules. type cannot be changed here, and the key never changes on rename.
Segment key (API key) or UUID (session).
Replacement rule tree for a dynamic segment. Recursive DSL, kept permissive.
New display name. The key is unaffected.
The Segment object.
The updated segment or list.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X PUT "https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
-H "X-API-Key: $SONARSEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme onboarding",
"definition": {}
}'const res = await fetch('https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80', {
method: 'PUT',
headers: {
'X-API-Key': process.env.SONARSEND_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Acme onboarding",
"definition": {}
}),
});
const data = await res.json();{
"id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"key": "welcome-series",
"segment_key": "welcome-series",
"tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"name": "Acme onboarding",
"type": "dynamic"
}Delete a segment or list
Deletes the segment or list. Scope-gated by type (lists:write for a list, segments:write for a segment). For a static list this also drops its membership rows; the contacts themselves are untouched. Deleting a segment does not delete any contact. Returns 204 with no body.
Segment key (API key) or UUID (session).
Deleted.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X DELETE "https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80', {
method: 'DELETE',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();Recompute cached counts
Forces a fresh evaluation of the segment or list and persists the result, so the next read returns current numbers instead of the cached (possibly stale) ones.
Returns active (the sendable count — excludes unsubscribed, bounced, complained, DNC and suppressed), total (the raw match count) and evaluated_at. total − active is the unsendable remainder.
The route requires segments:write. Refreshing a static list additionally requires lists:write (the per-target type check applies here too), so a list refresh needs both scopes.
Segment key (API key) or UUID (session).
Sendable member count.
When this evaluation ran.
Raw match / membership count.
The freshly computed counts.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X POST "https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80/refresh-count" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80/refresh-count', {
method: 'POST',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"active": 0,
"total": 0,
"evaluated_at": "2026-01-15T09:30:00.000Z"
}Preview members
Returns the current member count and a sample of matching contacts — for eyeballing an audience before you send to it. Works for both segments and lists. Requires segments:read regardless of the target's type (unlike the single-segment GET, this endpoint does not additionally gate on lists:read).
The sample is capped: limit defaults to 25 and is clamped to at most 100. For the full membership use GET /{id}/members (static lists) or export the audience.
Segment key (API key) or UUID (session).
Max sample rows. Default 25, clamped to 100.
The Contact object.
The total count and a capped sample of members.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X GET "https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80/preview" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80/preview', {
method: 'GET',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"count": 0,
"members": [
{
"id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"email": "jamie@example.com",
"first_name": "Jamie",
"last_name": "Reed",
"title": "Head of Growth"
}
]
}Check static-list membership
Given up to 100 contact IDs, returns each one's static list memberships. Dynamic segments are not included — they have no materialised membership, so this only answers "which lists is this contact on". Requires lists:read.
The response is { memberships }, an object keyed by contact ID. Every ID you send gets a key (an empty array when the contact is on no lists). For API-key callers each entry is { name, segment_key }.
Contact UUIDs, 1–100 per call.
Keyed by contact ID; each value is an array of the lists that contact belongs to.
Per-contact static-list memberships.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X POST "https://api.sonarsend.com/t/acme/api/segments/membership-check" \
-H "X-API-Key: $SONARSEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contact_ids": [
"string"
]
}'const res = await fetch('https://api.sonarsend.com/t/acme/api/segments/membership-check', {
method: 'POST',
headers: {
'X-API-Key': process.env.SONARSEND_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"contact_ids": [
"string"
]
}),
});
const data = await res.json();{
"memberships": {}
}List static-list members
A cursor-paged list of the contacts on a static list. Requires lists:read, and is only valid for static lists (400 NOT_STATIC for a dynamic segment — preview or export those instead). Page by passing the previous response's next_cursor back as cursor; has_more is false and next_cursor null on the last page. limit defaults to 50 and is clamped to 1–200. search matches across email, first and last name. Each row is a full contact plus a member_added_at timestamp.
List key (API key) or UUID (session).
Opaque next_cursor from the previous page. Omit for the first page.
Page size. Default 50, clamped to 1–200.
Free-text match across email, first and last name.
A page of Contact objects, under data.
A page of list members. Each contact carries an extra member_added_at.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X GET "https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80/members" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80/members', {
method: 'GET',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"data": [
{
"id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
"email": "jamie@example.com",
"first_name": "Jamie",
"last_name": "Reed",
"title": "Head of Growth"
}
],
"next_cursor": "eyJpZCI6IjljMjE4OTVkIn0",
"has_more": true
}Add members to a static list
Adds contacts to a static list. Requires lists:write. Only valid for static lists — calling it on a dynamic segment returns 400 NOT_STATIC (dynamic membership is rule-driven, not set by hand).
Idempotent per contact: adding one already on the list is a no-op and does not re-fire the segment-entered automation. Up to 1000 IDs per call. Returns { added } — note that count reflects the rows written, which may include re-adds; the entered event fires only for genuinely new members.
List key (API key) or UUID (session).
Contact UUIDs to add, 1–1000 per call.
How many rows were added.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X POST "https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80/members" \
-H "X-API-Key: $SONARSEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contact_ids": [
"string"
]
}'const res = await fetch('https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80/members', {
method: 'POST',
headers: {
'X-API-Key': process.env.SONARSEND_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"contact_ids": [
"string"
]
}),
});
const data = await res.json();{
"added": 0
}Remove members from a static list
Removes contacts from a static list. Requires lists:write, and like add is only valid for static lists (400 NOT_STATIC otherwise). The contacts themselves are not deleted. Returns { removed }; removing a contact that was not a member simply does not count. The segment-exited automation fires only for contacts that were actually on the list.
List key (API key) or UUID (session).
Contact UUIDs to remove.
How many rows were removed.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X DELETE "https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80/members" \
-H "X-API-Key: $SONARSEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contact_ids": [
"string"
]
}'const res = await fetch('https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80/members', {
method: 'DELETE',
headers: {
'X-API-Key': process.env.SONARSEND_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"contact_ids": [
"string"
]
}),
});
const data = await res.json();{
"removed": 0
}