EnvlopedDocs

Segments

Build live, nested audience rules over contact fields, list membership and engagement

Segments

A segment is a saved rule, not a saved list of people. It stores a definition and re-resolves every time it is counted, previewed, or sent to — including at the moment a scheduled campaign actually fires, not when it was scheduled. A contact who matches tomorrow is included tomorrow, with no intervention.

All endpoints require an Authorization: Bearer YOUR_API_KEY header.

The segment object

{
  "id": "sg_abc123",
  "name": "Singapore · women · 25–35",
  "description": null,
  "definition": { "op": "and", "children": [ /* … */ ] },
  "condition_count": 3,
  "created_at": "2026-08-09T10:30:00Z",
  "updated_at": "2026-08-09T10:30:00Z"
}

The definition grammar

A definition is a nested boolean tree. Groups have an op (and / or) and children; leaves have a kind.

{
  "op": "and",
  "children": [
    { "op": "or", "children": [
      { "kind": "attribute", "field": "city", "operator": "eq", "value": "Singapore" },
      { "kind": "attribute", "field": "city", "operator": "eq", "value": "Kuala Lumpur" }
    ]},
    { "kind": "attribute", "field": "date_of_birth", "operator": "age_between", "value": [25, 35] },
    { "kind": "attribute", "field": "interested_in", "operator": "contains_any", "value": ["women"] },
    { "kind": "list", "operator": "in_list", "value": "ls_newsletter" },
    { "kind": "engagement", "operator": "opened_in_last_days", "value": 30 },
    { "kind": "contact", "field": "status", "operator": "eq", "value": "subscribed" }
  ]
}

Limits: 25 conditions, nested up to 3 levels. Exceeding either returns 422 naming the limit. An empty group is an error — neither "matches everyone" nor "matches nobody" is safe to guess.

Leaf kinds

kindTargetsOperators
attributeA registered contact field by field keyPer the field's type
contactCore columns: status, created_at, email_domaineq, neq, in, not_in, before, after, between, in_last_days
listList membershipin_list, not_in_list
engagementOpens, clicks and receiptsopened_in_last_days, clicked_in_last_days, not_opened_in_last_days, opened_campaign, clicked_campaign, received_campaign

Age

age_between is an operator on a date field and takes [min, max] inclusive:

{ "kind": "attribute", "field": "date_of_birth", "operator": "age_between", "value": [20, 35] }

It compiles to a birthdate window evaluated against the current date, so nobody has to re-run anything on a birthday.

Two semantics worth knowing

Negative attribute operators require the attribute to be present. neq, not_in and not_contains match only contacts whose attribute is set and different. A contact with the attribute missing does not match. "Not X" means we know, and it isn't X — never we don't know. If 3% of your contacts have no gender recorded, gender neq male will not sweep them in.

Engagement negatives are the opposite. not_opened_in_last_days does match a contact with no events at all, because there the absence of a row is the fact: they did not open.

Opens are honest opens — proxy/prefetch opens are excluded, matching the campaign report.

Create a segment

POST /v1/segments
ParameterTypeRequired
namestringYes
descriptionstring | nullNo
definitionobjectYes
curl -X POST https://api.envloped.com/v1/segments \
  -H "Authorization: Bearer en_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Singapore 25-35",
    "definition": {
      "op": "and",
      "children": [
        { "kind": "attribute", "field": "city", "operator": "eq", "value": "Singapore" },
        { "kind": "attribute", "field": "date_of_birth", "operator": "age_between", "value": [25, 35] }
      ]
    }
  }'

The definition is normalised as it is validated — what is stored is canonical (trimmed strings, coerced numbers, de-duplicated arrays), so read it back rather than assuming it round-trips byte-for-byte.

Errors

StatuserrorMeaning
422unknown_fieldA leaf references a field key that is not registered, or has been archived.
422invalid_segmentThe tree is malformed, exceeds a limit, or uses an operator the field's type does not support.
409duplicate_segmentA live segment with that name already exists.

Both 422s include an errors array listing every problem found, not just the first.

Other endpoints

MethodPathPurpose
GET/v1/segmentsAll live segments.
GET/v1/segments/:idOne segment.
PATCH/v1/segments/:idRename, re-describe, or replace the definition.
DELETE/v1/segments/:idSoft delete. Campaigns that already sent keep their records.
GET/v1/segments/:id/countHow many contacts match right now.
GET/v1/segments/:id/preview?limit=25Count plus a sample of real contacts.
POST/v1/segments/previewEvaluate an unsaved definition — the endpoint the visual builder uses.
curl "https://api.envloped.com/v1/segments/sg_abc123/count" \
  -H "Authorization: Bearer en_abc123..."
# { "id": "sg_abc123", "count": 4210 }

What the count does and does not include

count answers exactly one question: how many contacts match this definition. It does not subtract unsubscribes, suppressions, or contacts that are not eligible to be mailed.

The number that matters before a send is the campaign-level eligible figure from GET /v1/campaigns/:id/audience/preview, which layers those gates on top of this same rule. Conflating the two would make the segment count lie about what the segment is.

On this page