# Segments

Source: https://kirora.io/docs/api/reference/segments

A segment is an audience described by rules, such as "ordered twice in 90 days", evaluated when it is used. Unlike a list, its membership changes as contacts and their events change, so a count or preview is true only for that moment. See [Objects and rules](https://kirora.io/docs/api/models#segment-rules) for the rule grammar. A contact's segments: `GET /v1/contacts/{id}/segments`.

## Endpoints

- [List segments](https://kirora.io/docs/api/reference/listSegments): `GET /v1/segments`
- [Create a segment](https://kirora.io/docs/api/reference/createSegment): `POST /v1/segments`
- [Retrieve a segment](https://kirora.io/docs/api/reference/getSegment): `GET /v1/segments/{id}`
- [Update a segment](https://kirora.io/docs/api/reference/updateSegment): `PATCH /v1/segments/{id}`
- [Delete a segment](https://kirora.io/docs/api/reference/deleteSegment): `DELETE /v1/segments/{id}`
- [Preview segment rules](https://kirora.io/docs/api/reference/previewSegment): `POST /v1/segments/preview`
- [List contacts in a segment](https://kirora.io/docs/api/reference/listSegmentContacts): `GET /v1/segments/{id}/contacts`
- [List who joined or left a segment on a day](https://kirora.io/docs/api/reference/listSegmentMembershipChanges): `GET /v1/segments/{id}/membership-changes`
- [Retrieve a segment's size history](https://kirora.io/docs/api/reference/getSegmentSizeHistory): `GET /v1/segments/{id}/size-history`
- [Retrieve a segment's usage](https://kirora.io/docs/api/reference/getSegmentUsage): `GET /v1/segments/{id}/usage`

## The Segment object

```json
{
  "id": "0192f3a3-2c4d-7f6e-9b32-5a7b8c9d0e1f",
  "name": "Lapsed customers",
  "description": "Newsletter readers who have not ordered in 90 days, for the win-back offer",
  "rules": {
    "all": [
      {
        "type": "event",
        "name": "Order Completed",
        "count": {
          "operator": "equals",
          "value": 0
        },
        "withinDays": 90
      },
      {
        "type": "list",
        "listIds": [
          "0192f3a2-1b3c-7e5d-8a21-4f6a7b8c9d0e"
        ],
        "member": true
      }
    ]
  },
  "memberCount": 214,
  "memberCountedAt": "2026-10-01T03:00:00.000Z",
  "tags": [
    "Win-back"
  ],
  "createdAt": "2026-09-15T10:00:00.000Z",
  "updatedAt": "2026-09-15T10:00:00.000Z"
}
```

- `id` (string, required, uuid)
- `name` (string, required)
- `description` (string | null, required)
- `rules` ([object](https://kirora.io/docs/api/models#segment-rules), required)
  - Any of: **Match all**
    - `all` (object[], required, at least 1 item, at most 20 items) — Every rule or nested group must match.
      - Any of: **Attribute**
        - `type` ("attribute", required)
        - `field` (string, required, pattern `^attributes\.[\p{L}\p{N}\p{M}_.-]{1,64}$`) — `attributes.<key>`, e.g. `attributes.plan`.
        - `operator` (string, required, one of `equals`, `notEquals`, `contains`, `notContains`, `startsWith`, `exists`, `notExists`, `before`, `after`, `gt`, `gte`, `lt`, `lte`, `in`, `withinLastDays`, `moreThanDaysAgo`, `inNextDays`, `anniversaryInNextDays`, `anniversaryThisMonth`) — How to compare. Text comparisons (`equals`, `notEquals`, `contains`, `notContains`, `startsWith`, `in`) ignore case and accents ("jose" matches "José"). `notEquals` and `notContains` also match contacts without the value. Date operators (`before`, `after`, `withinLastDays`, `moreThanDaysAgo`) compare as dates and times in UTC: a stored ISO-8601 date is the start of that day, `before` means earlier than the given day and `after` later than it, and a value that is not an ISO-8601 date or date and time never matches. `inNextDays` is a date from today to that many days ahead. `anniversaryInNextDays` (0 = today) and `anniversaryThisMonth` ignore the year, so they suit birthdays, and also read a yearless `--MM-DD`; a 29 February counts on 28 February in other years. Days are UTC days; these three are for attributes and dates, not contact fields.
        - `value` (string, optional, up to 255 characters) — What to compare with, always as text: `"pro"`, `"3"`, `"true"`. Equality compares the stored value as text, so a stored 5 matches `"5"`. An ISO date for `before` and `after`; a number for `gt`, `gte`, `lt` and `lte`; a whole number of days (1 to 3650) for `withinLastDays`, `moreThanDaysAgo` and `inNextDays`, and 0 to 365 for `anniversaryInNextDays`. Omit for `in`, `exists`, `notExists` and `anniversaryThisMonth`.
        - `values` (string[], optional, at least 1 item, at most 100 items) — For `in` only: up to 100 values, each as text, any of which matches.
      - Any of: **Contact field**
        - `type` ("contact", required)
        - `field` (string, required, one of `email`, `firstName`, `lastName`, `phone`, `timezone`, `externalId`, `consent.email`, `consent.push`, `country`, `locale`, `rfmGroup`, `rfmRecency`, `rfmFrequency`, `rfmMonetary`, `purchaseCount`, `totalSpent`, `lastPurchasedAt`, `createdAt`) — The contact column to test. Each takes these operators, and is refused with others: `email`: equals, notEquals, contains, startsWith, exists, notExists, in; `firstName`: equals, notEquals, contains, startsWith, exists, notExists, in; `lastName`: equals, notEquals, contains, startsWith, exists, notExists, in; `phone`: equals, notEquals, contains, startsWith, exists, notExists, in; `timezone`: equals, notEquals, contains, startsWith, exists, notExists, in; `externalId`: equals, notEquals, contains, startsWith, exists, notExists, in; `consent.email`: equals; `consent.push`: equals; `country`: equals, notEquals, in, exists, notExists; `locale`: equals, notEquals, startsWith, in, exists, notExists; `rfmGroup`: equals, notEquals, in, exists, notExists; `rfmRecency`: equals, notEquals, in, exists, notExists, gt, gte, lt, lte; `rfmFrequency`: equals, notEquals, in, exists, notExists, gt, gte, lt, lte; `rfmMonetary`: equals, notEquals, in, exists, notExists, gt, gte, lt, lte; `purchaseCount`: equals, gt, gte, lt, lte; `totalSpent`: equals, gt, gte, lt, lte; `lastPurchasedAt`: before, after, withinLastDays, moreThanDaysAgo, exists, notExists; `createdAt`: before, after, withinLastDays, moreThanDaysAgo. `phone` holds +country numbers: write `equals`, `notEquals` and `in` values with their country code. Consent takes `"true"` or `"false"`. Purchases: `purchaseCount`, `totalSpent` (in the main currency) and `lastPurchasedAt` are a customer's totals of the workspace's purchase event (`purchaseEvent` in workspace settings: an order, a paid invoice, a booking), recomputed daily; the contact's stored `totalSpent` is null when the workspace's purchases carry no amount. In comparisons on `purchaseCount` and `totalSpent` a missing count or spend is 0, so someone who never purchased has 0 purchases and 0 spent. RFM: `rfmGroup` and its scores `rfmRecency`, `rfmFrequency` and `rfmMonetary` (monetary value: how much they spend) come from the same daily pass. A score is a whole number from 1 to 5, written as text (`"5"`; `values` of them for `in`), 5 being the most recent, most frequent or highest spending; any other value is refused with 400. Someone who never purchased has no `rfmGroup` or scores: on those four fields they match only `notExists` and `notEquals`. So `rfmRecency` `notEquals` 5 includes everyone who never purchased; add an `exists` rule on the same field to leave them out (bought, but not recently).
        - `operator` (string, required, one of `equals`, `notEquals`, `contains`, `notContains`, `startsWith`, `exists`, `notExists`, `before`, `after`, `gt`, `gte`, `lt`, `lte`, `in`, `withinLastDays`, `moreThanDaysAgo`, `inNextDays`, `anniversaryInNextDays`, `anniversaryThisMonth`) — How to compare. Text comparisons (`equals`, `notEquals`, `contains`, `notContains`, `startsWith`, `in`) ignore case and accents ("jose" matches "José"). `notEquals` and `notContains` also match contacts without the value. Date operators (`before`, `after`, `withinLastDays`, `moreThanDaysAgo`) compare as dates and times in UTC: a stored ISO-8601 date is the start of that day, `before` means earlier than the given day and `after` later than it, and a value that is not an ISO-8601 date or date and time never matches. `inNextDays` is a date from today to that many days ahead. `anniversaryInNextDays` (0 = today) and `anniversaryThisMonth` ignore the year, so they suit birthdays, and also read a yearless `--MM-DD`; a 29 February counts on 28 February in other years. Days are UTC days; these three are for attributes and dates, not contact fields.
        - `value` (string, optional, up to 255 characters) — What to compare with, always as text: `"pro"`, `"3"`, `"true"`. Equality compares the stored value as text, so a stored 5 matches `"5"`. An ISO date for `before` and `after`; a number for `gt`, `gte`, `lt` and `lte`; a whole number of days (1 to 3650) for `withinLastDays`, `moreThanDaysAgo` and `inNextDays`, and 0 to 365 for `anniversaryInNextDays`. Omit for `in`, `exists`, `notExists` and `anniversaryThisMonth`.
        - `values` (string[], optional, at least 1 item, at most 100 items) — For `in` only: up to 100 values, each as text, any of which matches.
      - Any of: **Event**
        - `type` ("event", required)
        - `name` (string, required, 1–128 characters) — The event name.
        - `withinDays` (integer, optional, min > 0, max 3650) — Only events from the last `withinDays` days.
        - `notFollowedBy` (string, optional, 1–128 characters) — Only when this event name did not follow it afterwards.
        - `count` (object, optional) — How often; defaults to at least once. `{ operator: equals, value: 0 }` is "has not done".
        - `where` (object[], optional, at least 1 item, at most 5 items) — Property conditions the event must meet, up to 5; `count` counts only events meeting all of them.
      - Any of: **Message activity**
        - `type` ("activity", required)
        - `activity` (string, required, one of `sent`, `notSent`, `opened`, `clicked`, `notOpened`, `notClicked`, `bounced`) — `notOpened` and `notClicked`: sent, and not opened or clicked. Opens include machine opens (see `openedByMachine`).
        - `count` (object, optional) — How many messages had this activity; defaults to at least one. Not with `notSent`.
        - `link` (string, optional, 1–500 characters) — With `clicked` or `notClicked`: only clicks on a link whose address contains this text, ignoring case.
        - `channel` (string, optional, one of `email`, `push`) — Only messages on this channel (opened a push, not an email); any when left out.
        - `campaignId` (string, optional, uuid) — Only messages of this campaign.
        - `tagId` (string, optional, uuid) — Only messages of campaigns or journeys carrying this tag, as they are tagged when the rule is evaluated.
        - `journeyStepId` (string, optional, pattern `^[a-z0-9][a-z0-9_-]{0,63}$`) — Inside a journey only: the message sent by the step with this id in the contact's current enrollment. Matches nobody outside a journey.
        - `withinDays` (integer, optional, min > 0, max 3650) — Only activity from the last `withinDays` days. For `notOpened` and `notClicked` it is a waiting time instead: only messages sent at least this many days ago count, so each has had that long to be opened or clicked.
      - Any of: **Push installation**
        - `type` ("installation", required)
        - `has` (string, required, one of `active`, `none`) — `active`: reachable by push (granted, active, not invalidated); `none`: not.
        - `pushProjectId` (string, optional, uuid) — Only devices of this push project; any when omitted.
        - `platform` (string, optional, one of `ios`, `android`, `web`) — Only devices on this platform; any when omitted.
      - Any of: **List membership**
        - `type` ("list", required)
        - `listIds` (string[], required, at least 1 item, at most 50 items) — One list or several: with `member: true` in any of them, with `false` in none of them. Each must be one of the workspace's lists when the rules are saved.
        - `member` (boolean, required) — True for in the lists, false for not in them.
      - Any of: **Segment membership**
        - `type` ("segment", required)
        - `segmentId` (string, required, uuid)
        - `member` (boolean, required) — True for in the segment, false for not in it.
      - Any of: **Topic**
        - `type` ("topic", required)
        - `topicId` (string, required, uuid) — One of the workspace's topics when the rules are saved.
        - `channel` (string, optional, one of `email`, `push`) — Reads the contact's choice for this channel, which overrides their topic-wide choice. Without it, the rule reads the topic-wide choice, which a contact's `subscribed` shows.
        - `subscribed` (boolean, required) — True for contacts the topic reaches, false for those it does not.
      - Any of: **Match all**
        - `all` (object[], required, at least 1 item, at most 20 items) — Every rule must match.
      - Any of: **Match any**
        - `any` (object[], required, at least 1 item, at most 20 items) — At least one rule must match.
  - Any of: **Match any**
    - `any` (object[], required, at least 1 item, at most 20 items) — At least one rule or nested group must match.
      - Any of: **Attribute**
        - `type` ("attribute", required)
        - `field` (string, required, pattern `^attributes\.[\p{L}\p{N}\p{M}_.-]{1,64}$`) — `attributes.<key>`, e.g. `attributes.plan`.
        - `operator` (string, required, one of `equals`, `notEquals`, `contains`, `notContains`, `startsWith`, `exists`, `notExists`, `before`, `after`, `gt`, `gte`, `lt`, `lte`, `in`, `withinLastDays`, `moreThanDaysAgo`, `inNextDays`, `anniversaryInNextDays`, `anniversaryThisMonth`) — How to compare. Text comparisons (`equals`, `notEquals`, `contains`, `notContains`, `startsWith`, `in`) ignore case and accents ("jose" matches "José"). `notEquals` and `notContains` also match contacts without the value. Date operators (`before`, `after`, `withinLastDays`, `moreThanDaysAgo`) compare as dates and times in UTC: a stored ISO-8601 date is the start of that day, `before` means earlier than the given day and `after` later than it, and a value that is not an ISO-8601 date or date and time never matches. `inNextDays` is a date from today to that many days ahead. `anniversaryInNextDays` (0 = today) and `anniversaryThisMonth` ignore the year, so they suit birthdays, and also read a yearless `--MM-DD`; a 29 February counts on 28 February in other years. Days are UTC days; these three are for attributes and dates, not contact fields.
        - `value` (string, optional, up to 255 characters) — What to compare with, always as text: `"pro"`, `"3"`, `"true"`. Equality compares the stored value as text, so a stored 5 matches `"5"`. An ISO date for `before` and `after`; a number for `gt`, `gte`, `lt` and `lte`; a whole number of days (1 to 3650) for `withinLastDays`, `moreThanDaysAgo` and `inNextDays`, and 0 to 365 for `anniversaryInNextDays`. Omit for `in`, `exists`, `notExists` and `anniversaryThisMonth`.
        - `values` (string[], optional, at least 1 item, at most 100 items) — For `in` only: up to 100 values, each as text, any of which matches.
      - Any of: **Contact field**
        - `type` ("contact", required)
        - `field` (string, required, one of `email`, `firstName`, `lastName`, `phone`, `timezone`, `externalId`, `consent.email`, `consent.push`, `country`, `locale`, `rfmGroup`, `rfmRecency`, `rfmFrequency`, `rfmMonetary`, `purchaseCount`, `totalSpent`, `lastPurchasedAt`, `createdAt`) — The contact column to test. Each takes these operators, and is refused with others: `email`: equals, notEquals, contains, startsWith, exists, notExists, in; `firstName`: equals, notEquals, contains, startsWith, exists, notExists, in; `lastName`: equals, notEquals, contains, startsWith, exists, notExists, in; `phone`: equals, notEquals, contains, startsWith, exists, notExists, in; `timezone`: equals, notEquals, contains, startsWith, exists, notExists, in; `externalId`: equals, notEquals, contains, startsWith, exists, notExists, in; `consent.email`: equals; `consent.push`: equals; `country`: equals, notEquals, in, exists, notExists; `locale`: equals, notEquals, startsWith, in, exists, notExists; `rfmGroup`: equals, notEquals, in, exists, notExists; `rfmRecency`: equals, notEquals, in, exists, notExists, gt, gte, lt, lte; `rfmFrequency`: equals, notEquals, in, exists, notExists, gt, gte, lt, lte; `rfmMonetary`: equals, notEquals, in, exists, notExists, gt, gte, lt, lte; `purchaseCount`: equals, gt, gte, lt, lte; `totalSpent`: equals, gt, gte, lt, lte; `lastPurchasedAt`: before, after, withinLastDays, moreThanDaysAgo, exists, notExists; `createdAt`: before, after, withinLastDays, moreThanDaysAgo. `phone` holds +country numbers: write `equals`, `notEquals` and `in` values with their country code. Consent takes `"true"` or `"false"`. Purchases: `purchaseCount`, `totalSpent` (in the main currency) and `lastPurchasedAt` are a customer's totals of the workspace's purchase event (`purchaseEvent` in workspace settings: an order, a paid invoice, a booking), recomputed daily; the contact's stored `totalSpent` is null when the workspace's purchases carry no amount. In comparisons on `purchaseCount` and `totalSpent` a missing count or spend is 0, so someone who never purchased has 0 purchases and 0 spent. RFM: `rfmGroup` and its scores `rfmRecency`, `rfmFrequency` and `rfmMonetary` (monetary value: how much they spend) come from the same daily pass. A score is a whole number from 1 to 5, written as text (`"5"`; `values` of them for `in`), 5 being the most recent, most frequent or highest spending; any other value is refused with 400. Someone who never purchased has no `rfmGroup` or scores: on those four fields they match only `notExists` and `notEquals`. So `rfmRecency` `notEquals` 5 includes everyone who never purchased; add an `exists` rule on the same field to leave them out (bought, but not recently).
        - `operator` (string, required, one of `equals`, `notEquals`, `contains`, `notContains`, `startsWith`, `exists`, `notExists`, `before`, `after`, `gt`, `gte`, `lt`, `lte`, `in`, `withinLastDays`, `moreThanDaysAgo`, `inNextDays`, `anniversaryInNextDays`, `anniversaryThisMonth`) — How to compare. Text comparisons (`equals`, `notEquals`, `contains`, `notContains`, `startsWith`, `in`) ignore case and accents ("jose" matches "José"). `notEquals` and `notContains` also match contacts without the value. Date operators (`before`, `after`, `withinLastDays`, `moreThanDaysAgo`) compare as dates and times in UTC: a stored ISO-8601 date is the start of that day, `before` means earlier than the given day and `after` later than it, and a value that is not an ISO-8601 date or date and time never matches. `inNextDays` is a date from today to that many days ahead. `anniversaryInNextDays` (0 = today) and `anniversaryThisMonth` ignore the year, so they suit birthdays, and also read a yearless `--MM-DD`; a 29 February counts on 28 February in other years. Days are UTC days; these three are for attributes and dates, not contact fields.
        - `value` (string, optional, up to 255 characters) — What to compare with, always as text: `"pro"`, `"3"`, `"true"`. Equality compares the stored value as text, so a stored 5 matches `"5"`. An ISO date for `before` and `after`; a number for `gt`, `gte`, `lt` and `lte`; a whole number of days (1 to 3650) for `withinLastDays`, `moreThanDaysAgo` and `inNextDays`, and 0 to 365 for `anniversaryInNextDays`. Omit for `in`, `exists`, `notExists` and `anniversaryThisMonth`.
        - `values` (string[], optional, at least 1 item, at most 100 items) — For `in` only: up to 100 values, each as text, any of which matches.
      - Any of: **Event**
        - `type` ("event", required)
        - `name` (string, required, 1–128 characters) — The event name.
        - `withinDays` (integer, optional, min > 0, max 3650) — Only events from the last `withinDays` days.
        - `notFollowedBy` (string, optional, 1–128 characters) — Only when this event name did not follow it afterwards.
        - `count` (object, optional) — How often; defaults to at least once. `{ operator: equals, value: 0 }` is "has not done".
        - `where` (object[], optional, at least 1 item, at most 5 items) — Property conditions the event must meet, up to 5; `count` counts only events meeting all of them.
      - Any of: **Message activity**
        - `type` ("activity", required)
        - `activity` (string, required, one of `sent`, `notSent`, `opened`, `clicked`, `notOpened`, `notClicked`, `bounced`) — `notOpened` and `notClicked`: sent, and not opened or clicked. Opens include machine opens (see `openedByMachine`).
        - `count` (object, optional) — How many messages had this activity; defaults to at least one. Not with `notSent`.
        - `link` (string, optional, 1–500 characters) — With `clicked` or `notClicked`: only clicks on a link whose address contains this text, ignoring case.
        - `channel` (string, optional, one of `email`, `push`) — Only messages on this channel (opened a push, not an email); any when left out.
        - `campaignId` (string, optional, uuid) — Only messages of this campaign.
        - `tagId` (string, optional, uuid) — Only messages of campaigns or journeys carrying this tag, as they are tagged when the rule is evaluated.
        - `journeyStepId` (string, optional, pattern `^[a-z0-9][a-z0-9_-]{0,63}$`) — Inside a journey only: the message sent by the step with this id in the contact's current enrollment. Matches nobody outside a journey.
        - `withinDays` (integer, optional, min > 0, max 3650) — Only activity from the last `withinDays` days. For `notOpened` and `notClicked` it is a waiting time instead: only messages sent at least this many days ago count, so each has had that long to be opened or clicked.
      - Any of: **Push installation**
        - `type` ("installation", required)
        - `has` (string, required, one of `active`, `none`) — `active`: reachable by push (granted, active, not invalidated); `none`: not.
        - `pushProjectId` (string, optional, uuid) — Only devices of this push project; any when omitted.
        - `platform` (string, optional, one of `ios`, `android`, `web`) — Only devices on this platform; any when omitted.
      - Any of: **List membership**
        - `type` ("list", required)
        - `listIds` (string[], required, at least 1 item, at most 50 items) — One list or several: with `member: true` in any of them, with `false` in none of them. Each must be one of the workspace's lists when the rules are saved.
        - `member` (boolean, required) — True for in the lists, false for not in them.
      - Any of: **Segment membership**
        - `type` ("segment", required)
        - `segmentId` (string, required, uuid)
        - `member` (boolean, required) — True for in the segment, false for not in it.
      - Any of: **Topic**
        - `type` ("topic", required)
        - `topicId` (string, required, uuid) — One of the workspace's topics when the rules are saved.
        - `channel` (string, optional, one of `email`, `push`) — Reads the contact's choice for this channel, which overrides their topic-wide choice. Without it, the rule reads the topic-wide choice, which a contact's `subscribed` shows.
        - `subscribed` (boolean, required) — True for contacts the topic reaches, false for those it does not.
      - Any of: **Match all**
        - `all` (object[], required, at least 1 item, at most 20 items) — Every rule must match.
      - Any of: **Match any**
        - `any` (object[], required, at least 1 item, at most 20 items) — At least one rule must match.
- `memberCount` (integer | null, required, min 0) — Contacts in the stored membership, exact, from the background evaluation that runs about every 15 minutes; null until first counted, and again for a moment after the rules change.
- `memberCountedAt` (string | null, required, date-time) — When `memberCount` was taken.
- `tags` (string[], required) — Tag names, sorted without case.
- `createdAt` (string, required, date-time)
- `updatedAt` (string, required, date-time)
