SonarSend API Endpoints Lists & segments

Lists & segments

Static lists, dynamic segments, and their membership.

12 endpoints Base URL · https://api.sonarsend.com Auth · X-API-Key
The Segment object 14 fields
countinteger

Back-compat alias for the active (member_count) value, defaulting to 0. Present on the single-segment GET response.

created_atdate-time · nullable
definitionobject · nullable

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.

iduuid

Session responses only — replaced by segment_key for API-key callers.

keystring

Session responses only — the stable public key, surfaced as segment_key to API-key callers.

member_countinteger · nullable

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.

member_count_evaluated_atdate-time · nullable

When the cached counts were last computed.

member_count_totalinteger · nullable

Cached raw match count. member_count_total − member_count is the unsendable remainder.

namestring

Display label. Also the seed for key on creation; renaming does not change the key.

segment_keystring

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.

tagsstring[]

Free-text labels.

tenant_iduuid
typeenum

dynamic is rule-based (has a definition); static is an explicit membership list. Fixed at creation.

One of: dynamic, static
updated_atdate-time · nullable
The Contact object 62 fields
address_line1string · nullable
address_line2string · nullable
anonymized_atdate-time · nullable
bounce_typeenum · nullable
One of: hard, soft, null
category_subscriptionsobject

Category-level opt-out state, keyed by email category ID. Source of truth for categorised email types.

citystring · nullable
companystring · nullable
countrystring · nullable
created_atdate-time
custom_fieldsobject

Values for fields declared via the custom-fields API. Writing an undeclared key returns UNKNOWN_FIELD.

customer_scorenumber
deliverability_reasonstring · nullable
deliverability_statusenum
One of: healthy, watch, at_risk, suppressed
deliverability_updated_atdate-time · nullable
do_not_contactboolean
emailstring

The match key for upserts. Unique per tenant.

email_domainstring · nullable
external_idstring · nullable

Your own record key. Keep it populated so a re-sync can reconcile.

first_namestring · nullable
first_touch_atdate-time · nullable
first_touch_campaignstring · nullable
first_touch_countrystring · nullable
first_touch_landing_urlstring · nullable
first_touch_mediumstring · nullable
first_touch_referrerstring · nullable
first_touch_sourcestring · nullable
first_touch_timezonestring · nullable
global_statusenum

Account-wide sendability. suppressed is set by a soft delete.

One of: active, unsubscribed, bounced, complained, suppressed
iduuid

Canonical contact ID. Store this.

last_activity_atdate-time · nullable
last_email_clicked_atdate-time · nullable
last_email_opened_atdate-time · nullable
last_email_sent_atdate-time · nullable
last_namestring · nullable
last_page_viewed_atdate-time · nullable
last_touch_atdate-time · nullable
last_touch_campaignstring · nullable
last_touch_mediumstring · nullable
last_touch_sourcestring · nullable
member_added_atdate-time · nullable
mx_recordstring · nullable
phonestring · nullable
postal_codestring · nullable
prospect_scorenumber
score_updated_atdate-time · nullable
smtp_providerstring · nullable
sourcestring · nullable

Free-text origin label supplied by the caller.

stage_iduuid · nullable
statestring · nullable
status_before_suppressenum · nullable
One of: active, unsubscribed, bounced, complained, suppressed, null
subscriptionsobject

Per-email-type opt-in state. A missing key means subscribed.

suppressed_atdate-time · nullable
tagsstring[]
tenant_iduuid
timezonestring · nullable
titlestring · nullable
total_emails_clickedinteger
total_emails_openedinteger
total_emails_sentinteger
unsubscribed_atdate-time · nullable
updated_atdate-time
websitestring · nullable

Create a dynamic segment from broadcast engagement

POST /t/{slug}/api/segments/from-broadcast

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 clicked
  • openers / non_openers — received it and did / did not open
  • clickers / non_clickers — received it and did / did not click
  • bouncers / complainers / unsubscribers — bounced, complained, or unsubscribed on it
  • not_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 with INVALID_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).

broadcast_iduuidRequired

The broadcast whose engagement defines the audience.

criterionenumRequired

Which slice of the broadcast's audience to capture.

One of: engagers, openers, non_openers, clickers, non_clickers, bouncers, complainers, unsubscribers, not_received
namestringRequired

Name for the new segment. Its key is derived from this.

The Segment object.

201

The dynamic segment was created.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
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"
  }'
Response 201
{
  "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

GET /t/{slug}/api/segments/

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.

200

The segments and lists visible to this key.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/segments" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "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

POST /t/{slug}/api/segments/

Creates a dynamic segment or a static list. type defaults to dynamic when omitted.

  • Dynamic (type: "dynamic") requires a definition — omitting it fails with MISSING_DEFINITION. The definition is the recursive rule tree { logic, conditions[] }; see the Lists & segments guide for the condition grammar. Needs the segments:write scope.
  • Static (type: "static") ignores definition and starts empty — add members with POST /{id}/members. Needs the lists:write scope.

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.

namestringRequired

Display name. Seeds the generated key.

definitionobject

Rule tree for a dynamic segment. Required for dynamic, ignored for static. A recursive { logic, conditions[] } DSL — kept permissive; see the guide.

typeenum

Defaults to dynamic. Fixed once set.

One of: dynamic, static

The Segment object.

201

The segment or list was created.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
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": {}
  }'
Response 201
{
  "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

GET /t/{slug}/api/segments/{id}

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.

id string Required

Segment key (API key) or UUID (session).

The Segment object.

200

The segment or list, with cached counts.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "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

PUT /t/{slug}/api/segments/{id}

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.

id string Required

Segment key (API key) or UUID (session).

definitionobject

Replacement rule tree for a dynamic segment. Recursive DSL, kept permissive.

namestring

New display name. The key is unaffected.

The Segment object.

200

The updated segment or list.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
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": {}
  }'
Response 200
{
  "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

DELETE /t/{slug}/api/segments/{id}

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.

id string Required

Segment key (API key) or UUID (session).

204

Deleted.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X DELETE "https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY"

Recompute cached counts

POST /t/{slug}/api/segments/{id}/refresh-count

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.

id string Required

Segment key (API key) or UUID (session).

activeinteger

Sendable member count.

evaluated_atdate-time

When this evaluation ran.

totalinteger

Raw match / membership count.

200

The freshly computed counts.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
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"
Response 200
{
  "active": 0,
  "total": 0,
  "evaluated_at": "2026-01-15T09:30:00.000Z"
}

Preview members

GET /t/{slug}/api/segments/{id}/preview

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.

id string Required

Segment key (API key) or UUID (session).

limit string

Max sample rows. Default 25, clamped to 100.

The Contact object.

200

The total count and a capped sample of members.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80/preview" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "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

POST /t/{slug}/api/segments/membership-check

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_idsstring[]Required

Contact UUIDs, 1–100 per call.

membershipsobject

Keyed by contact ID; each value is an array of the lists that contact belongs to.

200

Per-contact static-list memberships.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
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"
    ]
  }'
Response 200
{
  "memberships": {}
}

List static-list members

GET /t/{slug}/api/segments/{id}/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.

id string Required

List key (API key) or UUID (session).

cursor string

Opaque next_cursor from the previous page. Omit for the first page.

limit string

Page size. Default 50, clamped to 1–200.

search string

Free-text match across email, first and last name.

A page of Contact objects, under data.

200

A page of list members. Each contact carries an extra member_added_at.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/segments/9c21895d-57f0-4a15-a1df-4dc6835d4f80/members" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "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

POST /t/{slug}/api/segments/{id}/members

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.

id string Required

List key (API key) or UUID (session).

contact_idsstring[]Required

Contact UUIDs to add, 1–1000 per call.

addedinteger
200

How many rows were added.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
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"
    ]
  }'
Response 200
{
  "added": 0
}

Remove members from a static list

DELETE /t/{slug}/api/segments/{id}/members

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.

id string Required

List key (API key) or UUID (session).

contact_idsstring[]Required

Contact UUIDs to remove.

removedinteger
200

How many rows were removed.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
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"
    ]
  }'
Response 200
{
  "removed": 0
}