Skip to content

Segments ​

A segment is everyone matching its filter, plus the contacts added to it by hand. Membership is recomputed on every read, so a contact that gains a matching property joins immediately. There is no rebuild that can fall behind.

A filter with no conditions contributes nobody, so such a segment holds only its hand-added members. This is how you build a fixed list.

The segment object ​

json
{
  "id": "…",
  "name": "Pro accounts",
  "slug": "pro_accounts",
  "description": "Paying customers who still accept mail",
  "filter": { "all": [ ] },
  "public_name": "Product news",
  "show_in_preferences": true,
  "created_at": "…",
  "updated_at": "…"
}

Create and update ​

POST   /v1/segments
GET    /v1/segments
GET    /v1/segments/{id}
PATCH  /v1/segments/{id}
DELETE /v1/segments/{id}
FieldRequiredNotes
nameyesUp to 200 characters.
slugyesUp to 64. Unique per organization.
descriptionnoA blank string is stored as null.
filternoThe tree below. Omit for a segment that holds only hand-added members.
public_namenoWhat recipients see on the preference page. Blank means unset.
show_in_preferencesnoDefault false. When true, members can opt out of this segment on the preference page.

The filter tree ​

A node is either a group — {"all": [...]} or {"any": [...]} — or a condition — {"field": …, "op": …, "value": …}. Groups nest.

json
{
  "all": [
    { "field": "unsubscribed", "op": "eq", "value": false },
    { "any": [
      { "field": "property:plan", "op": "eq", "value": "pro" },
      { "field": "email", "op": "ends_with", "value": "@acme.com" }
    ]}
  ]
}

Empty groups

Inside a filter that has conditions, an empty all matches everyone and an empty any matches no one. A filter with no conditions anywhere matches nobody, and the segment holds only its hand-added members. To target every contact, add a condition such as email contains @.

Fields and operators ​

There are three kinds of field, and each accepts a fixed set of operators. A combination outside the table is refused with notifyzr.segment.error.invalid_filter, and the message names the problem.

FieldOperatorsValue
emaileq ne contains starts_with ends_withA non-empty string
unsubscribedeqtrue or false. Reads the opt-out from all email only.
property:<key>, texteq ne contains starts_with ends_with set not_setA non-empty string
property:<key>, numbereq ne gt gte lt lte set not_setA number
property:<key>, booleaneq set not_settrue or false
property:<key>, dateeq before after set not_set"YYYY-MM-DD"

set and not_set ask whether the key is present at all, and take novalue.

A property: field must name a declared property definition — an undeclared key is refused, because the declaration is what says which operators are legal.

Limits ​

A filter nests at most 5 levels deep and holds at most 100 conditions.

How values are compared ​

Three details that decide what happens to imperfect data:

  • Numbers and booleans compare as their own types, never by casting. One malformed stored value cannot break the segment — that row simply drops out of the filter. This is the deliberate answer to "what happens after a property is retyped".
  • Dates compare as text, which is safe because they are stored YYYY-MM-DD and ISO dates sort lexicographically.
  • Nothing from the tree is ever put into SQL as text — not values, not property keys. Every one is a bound parameter.

Reading the members ​

GET /v1/segments/{id}/contacts

Paginated (page, per_page, default 25, max 100). Each row is a contact plus two flags: manual says it was added by hand, and segment_unsubscribed says it opted out of this segment's broadcasts. An opt-out does not end membership.

bash
curl "http://api.localhost/v1/segments/$SEGMENT_ID/contacts?page=1&per_page=100" \
  -H "X-API-Key: $NOTIFYZR_API_KEY"

Because membership is live, two calls a minute apart can legitimately return different totals. Do not treat a count as stable.

Adding and removing members by hand ​

POST   /v1/segments/{id}/members
DELETE /v1/segments/{id}/members/{contactId}
bash
curl -X POST "http://api.localhost/v1/segments/$SEGMENT_ID/members" \
  -H "X-API-Key: $NOTIFYZR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contact_ids": ["3f2504e0-4f89-41d3-9a0c-0305e82c3301"]}'

contact_ids takes up to 1,000 ids. If any id is not a contact of your organization, nothing is added and the call returns 422 notifyzr.segment.error.unknown_contacts. Contacts already added are left alone, and the response reports added and already_members.

Removing a member takes away the manual membership only. A contact that also matches the filter stays in the segment. A contact that was not added by hand returns 404 notifyzr.segment.error.member_not_found.

Opt-outs ​

A recipient can opt out of one segment on the preference page. That suppresses broadcasts to that segment only; the contact stays a member and still receives everything else.

Deleting ​

Deleting removes the definition, its hand-added members and its opt-outs. No contact is touched. A segment a broadcast points at cannot be deleted while that broadcast exists.

Released under the Apache 2.0 License.