[relaybell docs › Audience › Segments & topics](https://relaybell.com/docs/segments-topics) · [All docs as Markdown](https://relaybell.com/docs.md) · [Index for AI assistants](https://relaybell.com/llms.txt)

# Segments & topics

> The segment filter format and segment routes, topics, subscriber import/export and bulk labels.

## Segments {#segments}

`/api/v1/segments` — saved filters you can send to (`segmentId`), leave out of a send (`excludeSegmentId`), or attach to an RSS feed or a recurring schedule. A segment's `filters`, and the inline `filters` / `excludeFilters` of a send, use one format:

```json
{ "match": "all",                  // "all" = every rule must match (default), "any" = at least one
  "rules": [
    { "field": "country",  "op": "in", "value": ["IN", "US"] },
    { "field": "tag:plan", "op": "eq", "value": "pro" },
    { "field": "tag:vip",  "op": "exists" }
  ] }
```

| Part | Values |
| --- | --- |
| `field` | `country` (the 2-letter upper-case code from the visitor's IP), `language`, `url` (the subscriber's last page), `external_id` (or `externalId`), or `tag:KEY` for one of your tags. An unknown field matches **nobody** (the rule fails closed, so a typo can't turn a targeted send into a send to everyone). |
| `op` | `eq` (default; also used for an unknown op), `neq`, `contains` (case-insensitive), `in` (`value` is a list), `exists`, `not_exists` (no `value` needed). For a tag, `neq` also matches subscribers without that tag. |
| `value` | Compared as text, exactly: `eq`, `neq` and `in` are case-sensitive (only `contains` ignores case), so write country codes in upper case, e.g. `"IN"`. For `country`, `language`, `url` and `external_id`, `neq` doesn't match subscribers who have no value. |

| Request | What it does |
| --- | --- |
| **GET** `/api/v1/segments` | `{ segments: [{ id, name, filters, created_at }] }`, newest first. |
| **POST** `/api/v1/segments` | Body `{ name, filters }` → `201 { segment }`. `400 name_required` without a name. A segment needs at least one **complete** rule (a field — a `tag:` field with its key — and a value, unless the op is `exists` / `not_exists`): otherwise `400 segment_needs_a_rule`, because a segment with no rules would include every subscriber. |
| **PUT** `/api/v1/segments/:id` | Changes `name` and/or `filters` (the same rule check) → `{ segment }`. `400 nothing_to_update` when neither is sent; `404 not_found` for another website's id. |
| **DELETE** `/api/v1/segments/:id` | `{ ok: true }`. While an enabled RSS feed, an enabled recurring schedule, or a scheduled or queued send uses it (to include or to exclude), it answers `409 { "error": "segment_in_use", "feeds", "schedules", "pending" }` with how many of each. |
| **POST** `/api/v1/segments/preview` | Body `{ filters, topicId? }` → `{ count }`: how many subscribed **browsers** match right now (a person on two devices counts twice), optionally within a topic (id or slug). |
| **GET** `/api/v1/audience/segments` | Your saved segments with their size: `{ segments: [{ id, name, filters, created_at, count, lastUsedAt, sends, usedBy: { feeds, schedules } }] }`. `count` is the same number `/segments/preview` gives. Needs `subscribers:read`. |

Writes need `segments:write`. A send that names a segment which isn't in this website is refused (`400 segment_not_found`), and one whose segment is deleted before it goes out ends `failed` with `segment_missing` — a missing segment never means "everyone".

## Topics {#topics}

`/api/v1/topics` — preference-center categories. `GET` lists `{ id, name, slug, created_at, subscribers }`; `POST { name, slug? }` creates one (the slug is made from the name when left out; posting an existing slug renames that topic); `PUT /:id { name }` renames it (the slug never changes, since subscribers opt in by slug); `DELETE /:id`. Writes need `settings:write`. Subscribers opt in via `RelayBell.setTopics(["slug"])` / `showPreferences()`; target a send with `topicId` (id or slug).

## Subscribers {#subscribers}

- `GET /api/v1/subscribers/export` — CSV of subscribers. `GET /api/v1/subscribers/recent?limit=25` — the newest ones (up to 100).
- `POST /api/v1/subscribers/import` — body `{ subscribers: [{ externalId, tags: { … }, topics: ["slug"] }] }` (up to 10,000 rows) creates or enriches subscribers by external id → `{ ok, processed }` (`400 no_subscribers` for an empty list). Push subscriptions themselves can't be imported: each browser must opt in on your site.
- `GET /api/v1/subscribers/:externalId/profile` — one person (by external id or relaybell id): `{ subscriber, tags, endpoints, topics, sends, events, stats }`, with their last 50 deliveries/clicks and events; `/data` and `DELETE` are under [Compliance](https://relaybell.com/docs/compliance).
- `GET /api/v1/audience/subscribers` — the dashboard's paged, filterable list; `POST /api/v1/audience/subscribers/bulk-tag` `{ ids, set: { key: value }, remove: [key] }` (at most 20 labels at a time; `rb_` keys are reserved) and `/bulk-delete` `{ ids }` → `{ ok, deleted }`.
