# Segments API

Full parity with the UI. Segments are addressed by **key**, not by database id, so a definitions file can name one without knowing anything about your database.

Every endpoint needs a bearer token from **Settings → Agents & API**.

## Endpoints

| Method | Path | What it does |
| --- | --- | --- |
| GET | `/api/v1/segments` | Every segment with its live count |
| POST | `/api/v1/segments` | Create one |
| GET | `/api/v1/segments/:key` | One segment, with rules and where it's used |
| PATCH | `/api/v1/segments/:key` | Update it |
| DELETE | `/api/v1/segments/:key` | Delete it |
| GET | `/api/v1/segments/:key/count` | Just the number |
| GET | `/api/v1/segments/:key/members` | The people, paginated |
| POST | `/api/v1/segments/preview` | Check and count rules without saving them |

**Count and members are separate on purpose.** Asking "how many?" shouldn't make you pay for the list, and asking for the list shouldn't hand you forty thousand rows in one response. `members` is paginated, capped at 500 per page.

## Creating a segment

```
curl -X POST https://your-mimeo.com/api/v1/segments \
  -H "Authorization: Bearer $MIMEO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{\n    "name": "Engaged customers",\n    "description": "Bought something and opened recent mail.",\n    "rules": {\n      "type": "all",\n      "conditions": [\
        { "type": "total_spend", "op": "greater", "value": 0 },\
        { "type": "opened_email", "email_id": 12, "within_days": 60 },\
        { "type": "not", "condition": { "type": "suppressed" } }\
      ]\n    }\n  }'```

The key is derived from the name (`engaged_customers`) unless you supply one.

## Checking rules before you commit to them

`preview` validates and counts a rule set without creating anything — the API's answer to the editor's live count, and what a definitions-repo diff should call before applying a change.

```
curl -X POST https://your-mimeo.com/api/v1/segments/preview \
  -H "Authorization: Bearer $MIMEO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "rules": { "type": "has_tag", "tag": "customer" } }'```

```
{
  "count": 412,
  "total": 5310,
  "exact": true,
  "expensive": false,
  "validation": { "valid": true, "errors": [], "warnings": [] }
}
```

- `exact` — the whole rule set resolved in SQL.
- `expensive` — it needs a query per candidate person (a code predicate, or an event matched on its details).

## Errors name the fix

Invalid rules come back as `422` with messages written to be acted on rather than just reported:

```
{
  "errors": [\
    "This rule tests field \"plan_tier\", which is not a defined field. Create it under People → Fields first."\
  ],
  "validation": { "valid": false, "errors": [ … ] }
}
```

Deleting a segment something still references is refused, with `used_by` listing what would break.

## The rule vocabulary

The same condition types flows and guards use — see [the schema reference](/content/docs/reference/flow-schema.html) for the full list. Segments add `total_spend`, and support `within_days` on `received_email`, `opened_email` and `clicked_email`.

`email` compares the person's email address, ignoring case, with `equals`, `starts_with`, `ends_with` or `contains` — so "everyone at acme.com" is `{ "type": "email", "op": "ends_with", "value": "@acme.com" }`.

`in_segment` takes a `segment` key. A reference loop is rejected on save.

`subscribed_on` compares the date a person joined the list against a `value` like "2026-06-01", using `less` (before), `greater` (after) or `equals` (on). Both comparisons are strict. Everyone has the date, so this is a single condition with no null case to wrap:

```
{ "type": "all", "conditions": [\
    { "type": "subscribed_on", "op": "less", "value": "2026-06-01" },\
    { "type": "not", "condition": { "type": "suppressed" } }\
  ] }
```

Next: [The subscription API →](/content/docs/api/subscriptions.html)
