Documentation

Segments

Saved, named filters over one audience, by tags, engagement, custom properties and topic subscription, that campaigns target and you can count before sending.

A segment is a saved filter over one audience: "customers tagged vip who opened something in the last 90 days", for example. A campaign created with a segment_id mails only the people the segment matches, and the same definition can be reused across campaigns and counted before anything is sent. See Campaigns.

A segment stores its rules, not its members. Every time it is used, whether by counting it, listing its contacts or sending a campaign, it is evaluated again against the contacts as they are at that moment. Engagement windows are measured back from the moment of evaluation, so the same segment matches different people on different days. A campaign freezes its recipient list when it starts sending, so a running campaign is not affected by later changes.

How a segment matches

A contact matches when every one of these holds:

  1. They are on the segment's audience (audience_id is in their audience_ids).
  2. Their unsubscribed flag is false.
  3. Every rule the segment sets is satisfied. Rules left unset do not filter. Rules combine with AND; there is no OR between rules.
RuleMatches contacts who
tagscarry all of these tags.
exclude_tagscarry none of these tags. An exclusion wins over an inclusion: someone tagged both customer and churned is left out of a segment with tags: ["customer"] and exclude_tags: ["churned"].
opened_within_dayshave last_opened_at within the last N days (N × 24 hours before evaluation). Someone who has never opened does not match.
clicked_within_dayshave last_clicked_at within the last N days. Someone who has never clicked does not match.
exclude_unengaged_daysopened or clicked within the last N days, or have never been mailed (no last_sent_at), or were added within the last N days. last_sent_at is recorded each time a message to the address is accepted for delivery, so this leaves out people who have been on the list for the whole window, have been mailed, and have neither opened nor clicked.
engaged_within_daysopened, clicked, or were active in your product (last_active_at) within the last N days. Unlike exclude_unengaged_days there is no allowance for contacts never mailed or recently added, so on a list imported this month it keeps only people with evidence of interest rather than everyone. Set last_active_at on your contacts for it to see product activity.
propertiessatisfy every property filter in the array. See Property filters.
topic_keyare subscribed to this topic: their own preference when they have set one, otherwise the topic's default. The topic must exist when the segment is saved. If it is deleted later, the segment matches nobody.

Opens and clicks are only recorded for mail sent from a domain whose click tracking hostname (link.) is active; see domains. Contacts mailed only from a domain without it never get last_opened_at or last_clicked_at, so opened_within_days and clicked_within_days never match them, exclude_unengaged_days leaves them out once they have been mailed and on the list for longer than the window, and engaged_within_days matches them only through last_active_at. Check the count with segment metrics before using an engagement rule on such a list.

Tags in tags and exclude_tags are normalised when the segment is saved, the same way contact tags are: trimmed, lower-cased, runs of whitespace turned into a hyphen and cut to 60 characters.

Suppressions are not consulted when a segment is evaluated, only the unsubscribed flag. They are enforced when mail is sent, so a count can include an address that will be skipped at send time.

topic_key is checked when you save: a key that names no topic in the workspace is refused with 422 unknown_topic. The property in a filter is not checked against what contacts carry. A typo there is stored as written and quietly matches nobody (or, for not_equals and not_exists, everybody). Count the segment after creating it. A property that no contact can carry, because it is empty, contains . or a null character, or starts with $, is refused with 422.

Property filters

Each entry in properties tests one value in a contact's attributes.

FieldTypeRequiredDescription
propertystringyesThe key in the contact's attributes. It does not have to be declared as a contact property, though declaring one is what gives the values a type. Cannot be empty, contain . or a null character, or start with $.
operator"equals" | "not_equals" | "contains" | "gt" | "lt" | "exists" | "not_exists"yesHow to compare.
valuestring | number | booleannoWhat to compare against. Leave it out only for exists and not_exists. Left out of another operator, it compares against null: equals then matches contacts without the attribute, not_equals matches contacts with it, gt and lt match nobody, and contains matches every string value.
OperatorMatches contacts whose attribute
equalsis exactly value. The comparison is type-sensitive: 42 does not equal "42", and true does not equal "true".
not_equalsis anything other than value, including contacts who do not have the attribute at all.
containsis a string containing value as text, ignoring case. value is matched literally, not as a pattern. Numbers and booleans never match.
gtis greater than value.
ltis less than value.
existsis present and not null. value is ignored.
not_existsis missing or null. value is ignored.

gt and lt only compare values of the same kind: a number with a number, a string with a string. Strings compare character by character, so "9" is greater than "42". This is why a property's type matters: declare numeric properties as number so imported values are stored as numbers, and store dates as ISO 8601 strings so they sort in time order.

The segment object

Every field is always present. A rule that is not set is null, or [] for the list rules, so creating, retrieving, listing and updating a segment all return exactly the same fields.

FieldTypeDescription
idstringThe segment's id, a UUID. Pass it as segment_id when creating a campaign.
keystringThe key given at creation.
namestringDisplay name.
audience_idstringThe audience the segment filters.
opened_within_daysinteger | nullThe opened_within_days rule.
clicked_within_daysinteger | nullThe clicked_within_days rule.
exclude_unengaged_daysinteger | nullThe exclude_unengaged_days rule.
engaged_within_daysinteger | nullThe engaged_within_days rule.
propertiesarray of objectThe property filters, each with property, operator and value. value is null when none was given, as for exists and not_exists. [] when there are none.
tagsarray of stringThe tags rule, normalised. [] when not set.
exclude_tagsarray of stringThe exclude_tags rule, normalised. [] when not set.
topic_keystring | nullThe topic_key rule.
created_atstring (ISO 8601)When the segment was created.
updated_atstring (ISO 8601) | nullWhen the segment was last updated. null until the first update.
{
  "id": "4c9e2a7b-1d3f-4b8e-a6c5-7f2d9e1b3a58",
  "key": "active-pro",
  "name": "Active Pro customers",
  "audience_id": "b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e",
  "opened_within_days": 90,
  "clicked_within_days": null,
  "exclude_unengaged_days": null,
  "engaged_within_days": null,
  "properties": [
    { "property": "plan", "operator": "equals", "value": "pro" },
    { "property": "seats", "operator": "gt", "value": 3 }
  ],
  "tags": ["customer"],
  "exclude_tags": ["churned"],
  "topic_key": "product-updates",
  "created_at": "2026-09-10T09:00:00.000Z",
  "updated_at": null
}

List segments

GET /v1/segments · requires contacts:read

Every segment in the workspace, sorted by name, up to 500. There is no paging: next_cursor is always null, and has_more is true when 500 segments came back, which means there may be more than this endpoint can return.

Example

curl https://api.sendraven.ai/v1/segments \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

{
  "object": "list",
  "data": [
    {
      "id": "4c9e2a7b-1d3f-4b8e-a6c5-7f2d9e1b3a58",
      "key": "active-pro",
      "name": "Active Pro customers",
      "audience_id": "b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e",
      "opened_within_days": 90,
      "clicked_within_days": null,
      "exclude_unengaged_days": null,
      "engaged_within_days": null,
      "properties": [
        { "property": "plan", "operator": "equals", "value": "pro" },
        { "property": "seats", "operator": "gt", "value": 3 }
      ],
      "tags": ["customer"],
      "exclude_tags": ["churned"],
      "topic_key": "product-updates",
      "created_at": "2026-09-10T09:00:00.000Z",
      "updated_at": null
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Create a segment

POST /v1/segments · requires contacts:write

Saves a segment over one audience. Nothing is evaluated at this point; count it afterwards to check it matches who you expect.

name does not have to be unique, but key does, within the workspace: saving a second segment with a key already in use answers 409 conflict. Keep the returned id, which is what campaigns and every other segment call take.

Body

FieldTypeRequiredDefaultDescription
audience_idstringyesThe id of the audience to filter, from GET /v1/audiences. Must be an audience in this workspace. Surrounding whitespace is ignored.
keystringyesA short identifier, unique in the workspace: lower-case letters, digits and hyphens only, at least one character.
namestringyesDisplay name, 1 to 120 characters.
tagsarray of stringnoMatch only contacts carrying all of these. Up to 20, each at least 1 character.
exclude_tagsarray of stringnoLeave out contacts carrying any of these. Up to 20, each at least 1 character.
opened_within_daysintegernoOnly contacts who opened within this many days. 1 to 3650.
clicked_within_daysintegernoOnly contacts who clicked within this many days. 1 to 3650.
exclude_unengaged_daysintegernoLeave out contacts who have been mailed but neither opened nor clicked within this many days. Contacts added within the window, and contacts never mailed, are kept. 1 to 3650.
engaged_within_daysintegernoOnly contacts who opened, clicked, or were active in your product (last_active_at) within this many days. 1 to 3650.
propertiesarray of objectnoUp to 20 property filters, all of which must hold.
topic_keystringnoOnly contacts subscribed to the topic with this key. Must name an existing topic. Surrounding whitespace is trimmed, and "" means no topic rule.

Example

curl -X POST https://api.sendraven.ai/v1/segments \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "audience_id": "b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e",
    "key": "active-pro",
    "name": "Active Pro customers",
    "tags": ["Customer"],
    "exclude_tags": ["churned"],
    "opened_within_days": 90,
    "properties": [
      { "property": "plan", "operator": "equals", "value": "pro" },
      { "property": "seats", "operator": "gt", "value": 3 }
    ],
    "topic_key": "product-updates"
  }'

Response

201 Created

A segment object. Rules you did not set are null, or [] for the list rules.

{
  "id": "4c9e2a7b-1d3f-4b8e-a6c5-7f2d9e1b3a58",
  "key": "active-pro",
  "name": "Active Pro customers",
  "audience_id": "b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e",
  "opened_within_days": 90,
  "clicked_within_days": null,
  "exclude_unengaged_days": null,
  "engaged_within_days": null,
  "properties": [
    { "property": "plan", "operator": "equals", "value": "pro" },
    { "property": "seats", "operator": "gt", "value": 3 }
  ],
  "tags": ["customer"],
  "exclude_tags": ["churned"],
  "topic_key": "product-updates",
  "created_at": "2026-09-15T12:00:00.000Z",
  "updated_at": null
}

Errors

StatusTypeWhen
422invalid_requestA field fails validation (message Validation failed, with details).
422invalid_requestaudience_id is not an audience in this workspace. The message starts audience_id:, and when the value matches an audience's name it gives that audience's id.
422unknown_topictopic_key names no topic in this workspace. The message starts topic_key:.
409conflictAnother segment in the workspace already uses this key.

Retrieve a segment

GET /v1/segments/{id} · requires contacts:read

Returns one segment's definition. Passing a segment's name or key instead of its id answers 404, and when it matches a segment in the workspace (case-insensitively) the message gives its id.

Path parameters

ParameterDescription
idThe segment's id.

Example

curl https://api.sendraven.ai/v1/segments/4c9e2a7b-1d3f-4b8e-a6c5-7f2d9e1b3a58 \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

A segment object.

{
  "id": "4c9e2a7b-1d3f-4b8e-a6c5-7f2d9e1b3a58",
  "key": "active-pro",
  "name": "Active Pro customers",
  "audience_id": "b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e",
  "opened_within_days": 90,
  "clicked_within_days": null,
  "exclude_unengaged_days": null,
  "engaged_within_days": null,
  "properties": [
    { "property": "plan", "operator": "equals", "value": "pro" },
    { "property": "seats", "operator": "gt", "value": 3 }
  ],
  "tags": ["customer"],
  "exclude_tags": ["churned"],
  "topic_key": "product-updates",
  "created_at": "2026-09-10T09:00:00.000Z",
  "updated_at": null
}

Errors

StatusTypeWhen
404not_foundNo segment with that id in this workspace.

Update a segment

PATCH /v1/segments/{id} · requires contacts:write

Changes any of a segment's fields. Send only what should change; everything else stays as it was. Each field you send replaces the stored value whole: a properties array replaces every existing filter, and tags replaces the whole tag list.

What an update can and cannot do:

  • Clear a list rule by sending an empty array: "tags": [], "exclude_tags": [] or "properties": [].
  • Clear the topic rule by sending "topic_key": "". An empty key is treated as no topic, and reads back as null. Any other topic_key must name an existing topic.
  • Clear an engagement window by sending null: "opened_within_days": null, "clicked_within_days": null, "exclude_unengaged_days": null or "engaged_within_days": null. It reads back as null.
  • key cannot be changed. A body that contains key, even with the current value, is refused with 422 invalid_request (key: a segment's key cannot be changed; create a new segment) and nothing is changed. To use a different key, create a new segment.
  • Moving the segment to another audience with audience_id is allowed. A campaign already using the segment is not re-checked, so its segment can then filter a different audience from the campaign's own.

A campaign that has already started sending has frozen its recipients and is not affected by an update.

Path parameters

ParameterDescription
idThe segment's id.

Body

All fields are optional. Each has the same type and limits as on create.

FieldTypeRequiredDefaultDescription
audience_idstringnoMove the segment to this audience. Must be an audience in this workspace. Surrounding whitespace is ignored.
namestringno1 to 120 characters.
tagsarray of stringnoReplaces the tags rule. Up to 20.
exclude_tagsarray of stringnoReplaces the exclude_tags rule. Up to 20.
opened_within_daysinteger | nullno1 to 3650, or null to remove the rule.
clicked_within_daysinteger | nullno1 to 3650, or null to remove the rule.
exclude_unengaged_daysinteger | nullno1 to 3650, or null to remove the rule.
engaged_within_daysinteger | nullno1 to 3650, or null to remove the rule.
propertiesarray of objectnoReplaces every property filter. Up to 20.
topic_keystringnoReplaces the topic rule with an existing topic's key; "" removes it.

Example

curl -X PATCH https://api.sendraven.ai/v1/segments/4c9e2a7b-1d3f-4b8e-a6c5-7f2d9e1b3a58 \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "opened_within_days": 30,
    "exclude_tags": []
  }'

Response

200 OK

The updated segment object, now with updated_at set.

{
  "id": "4c9e2a7b-1d3f-4b8e-a6c5-7f2d9e1b3a58",
  "key": "active-pro",
  "name": "Active Pro customers",
  "audience_id": "b7e2c1d4-6a3f-4e8b-9c5d-1f2a3b4c5d6e",
  "opened_within_days": 30,
  "clicked_within_days": null,
  "exclude_unengaged_days": null,
  "engaged_within_days": null,
  "properties": [
    { "property": "plan", "operator": "equals", "value": "pro" },
    { "property": "seats", "operator": "gt", "value": 3 }
  ],
  "tags": ["customer"],
  "exclude_tags": [],
  "topic_key": "product-updates",
  "created_at": "2026-09-10T09:00:00.000Z",
  "updated_at": "2026-09-15T12:00:00.000Z"
}

Errors

StatusTypeWhen
404not_foundNo segment with that id in this workspace.
422invalid_requestThe body contains key. The message is key: a segment's key cannot be changed; create a new segment. Nothing is changed.
422invalid_requestA field fails validation (message Validation failed, with details).
422invalid_requestaudience_id is not an audience in this workspace. The message starts audience_id:.
422unknown_topictopic_key names no topic in this workspace. The message starts topic_key:. Nothing is changed.

Delete a segment

DELETE /v1/segments/{id} · requires contacts:write

Deletes the segment's definition. No contacts are touched.

A campaign that still refers to a deleted segment through segment_id resolves to nobody when it sends. Delete a segment only once no draft or scheduled campaign uses it.

Path parameters

ParameterDescription
idThe segment's id.

Example

curl -X DELETE https://api.sendraven.ai/v1/segments/4c9e2a7b-1d3f-4b8e-a6c5-7f2d9e1b3a58 \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

{
  "id": "4c9e2a7b-1d3f-4b8e-a6c5-7f2d9e1b3a58",
  "deleted": true
}

Errors

StatusTypeWhen
404not_foundNo segment with that id in this workspace.

Get segment metrics

GET /v1/segments/{id}/metrics · requires contacts:read

How many contacts the segment matches right now. Run it before building a campaign around a segment: a filter that matches nobody, or everybody, is much easier to spot here than after a send. The segment is evaluated in full on every call, so on a large audience this is not a cheap request.

Path parameters

ParameterDescription
idThe segment's id.

Example

curl https://api.sendraven.ai/v1/segments/4c9e2a7b-1d3f-4b8e-a6c5-7f2d9e1b3a58/metrics \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

FieldTypeDescription
idstringThe segment's id.
contact_countintegerContacts the segment matches at the time of the request.
{
  "id": "4c9e2a7b-1d3f-4b8e-a6c5-7f2d9e1b3a58",
  "contact_count": 312
}

Errors

StatusTypeWhen
404not_foundNo segment with that id in this workspace.

List the contacts in a segment

GET /v1/segments/{id}/contacts · requires contacts:read

The email addresses the segment matches right now, one page at a time. data is an array of address strings, not contact objects; look a person up with GET /v1/contacts?email= for the rest.

The list is cursor-paged like every other (pagination): newest contact first, by when the contact was created, the same order as GET /v1/contacts. Pass next_cursor back as cursor while has_more is true. Membership is worked out on each call, so a contact who stops matching (or starts) between two pages drops out of (or joins) the later pages. For the number of matches, use metrics; this list carries no count.

Path parameters

ParameterDescription
idThe segment's id.

Query parameters

ParameterTypeDefaultDescription
limitinteger200Page size, 1 to 1000. A larger value returns 1000, a fraction is rounded down, and a missing, zero, negative or non-numeric value uses 200. This list's range is larger than the other lists' because an address is small.
cursorstringnoneThe next_cursor from the previous page, passed back unchanged. An unreadable cursor returns the first page.

Example

curl "https://api.sendraven.ai/v1/segments/4c9e2a7b-1d3f-4b8e-a6c5-7f2d9e1b3a58/contacts?limit=3" \
  -H "Authorization: Bearer $SENDRAVEN_API_KEY"

Response

200 OK

{
  "object": "list",
  "data": ["ana@example.com", "bo@example.com", "cy@example.com"],
  "has_more": true,
  "next_cursor": "MjAyNi0wOS0xNVQxMjowMDowMC4wMDBafDdmMmQ5ZTFiLTNhNTgtNGM5ZS1hMmI3LTFkM2Y0YjhlYTZjNQ"
}

Errors

StatusTypeWhen
404not_foundNo segment with that id in this workspace.