# Segments API

Create, check and read segments by key — plus counting and listing members without either paying for the other.

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 |

> [!NOTE]
> **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 '{
    "name": "Engaged customers",
    "description": "Bought something and opened recent mail.",
    "rules": {
      "type": "all",
      "conditions": [
        { "type": "total_spend", "op": "greater", "value": 0 },
        { "type": "opened_email", "email_id": 12, "within_days": 60 },
        { "type": "not", "condition": { "type": "suppressed" } }
      ]
    }
  }'
```

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](https://mimeohq.com/docs/reference/flow-schema) 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" } }
  ] }
```
