# Segments

An audience. A segment is a filter over your contacts, a list of contacts added by hand, or both — and it is what a broadcast is sent to.

## The filter

A filter is a tree of `all`, `any` and `not` groups over conditions. A condition is either `{ field, op, value }` or `{ topic, subscription }`. Groups nest three deep and a filter holds at most twenty conditions.

A filter:

```text
{
  "any": [
    {
      "all": [
        {
          "field": "properties.plan",
          "op": "eq",
          "value": "trial"
        },
        {
          "field": "created_at",
          "op": "gte",
          "value": "2026-09-01T00:00:00Z"
        }
      ]
    },
    {
      "topic": "b6d24b8e-af0b-4c3c-be0c-359bbd97381e",
      "subscription": "opt_in"
    }
  ]
}
```

| Field | Type | Operators |
| --- | --- | --- |
| `email` | string | `eq`, `neq`, `contains`, `in` |
| `first_name` | string | `eq`, `neq`, `contains`, `exists`, `in` |
| `last_name` | string | `eq`, `neq`, `contains`, `exists`, `in` |
| `unsubscribed` | boolean | `eq` |
| `created_at` | date | `gt`, `gte`, `lt`, `lte` |
| `properties.<key>` | the property's type | Strings as `first_name`; numbers `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `exists` |

- The filter is checked when the segment is created: a property that is not declared, or an operator that does not suit its type, is `422 validation_error` with one entry in `errors[]` per condition.
- A filter is never changed. Rename a segment freely; for a different audience, create a new one, so a sent broadcast keeps naming the audience it went to.

## Membership

A contact is in a segment if the filter matches it *or* it was added explicitly. Membership is evaluated when it is read and when a broadcast is sent — never stored — so a contact who starts matching is in from that moment, and one who stops matching is out, unless they were added by hand. Deleted contacts are never members.

## Endpoints

### `POST /segments`

Name an audience, optionally defined by a filter over your contacts.

#### Body

| Field | Type | Description |
| --- | --- | --- |
| `name` (required) | string | Up to 200 characters, unique among live segments. |
| `filter` | object \| null | `{ all \| any \| not: [ … ] }` over `{ field, op, value }` and `{ topic, subscription }` conditions. Omit it for a segment with explicit membership only. |

Create a segment:

```sh
curl -X POST "https://api.rasket.com/segments" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Trial accounts",
  "filter": {
    "all": [
      {
        "field": "properties.plan",
        "op": "eq",
        "value": "trial"
      },
      {
        "field": "created_at",
        "op": "gte",
        "value": "2026-09-01T00:00:00Z"
      }
    ]
  }
}'
```

```ts
const response = await fetch("https://api.rasket.com/segments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Trial accounts",
    filter: {
      all: [
        {
          field: "properties.plan",
          op: "eq",
          value: "trial"
        },
        {
          field: "created_at",
          op: "gte",
          value: "2026-09-01T00:00:00Z"
        }
      ]
    }
  }),
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.post(
    "https://api.rasket.com/segments",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
    json={
    "name": "Trial accounts",
    "filter": {
      "all": [
        {
          "field": "properties.plan",
          "op": "eq",
          "value": "trial"
        },
        {
          "field": "created_at",
          "op": "gte",
          "value": "2026-09-01T00:00:00Z"
        }
      ]
    }
  },
)

id = response.json()["id"]
```

#### Response `201`

```json
{
  "object": "segment",
  "id": "78261eea-8f8b-4381-83c6-79fa7120f1cf"
}
```

- The filter is checked against your contact properties when the segment is created: an undeclared property, or an operator that does not suit its type, is `422 validation_error` with one `errors[]` entry per condition.
- Membership is evaluated when it is read and when a broadcast is sent, never stored — a contact that starts matching is in the segment from that moment.

### `GET /segments`

Every live segment, newest first.

#### Query parameters

| Field | Type | Description |
| --- | --- | --- |
| `limit` | integer | How many items to return, 1–100. Defaults to 20. |
| `after` | string | Return the page that follows this item ID. Mutually exclusive with `before`. |
| `before` | string | Return the page that precedes this item ID. Mutually exclusive with `after`. |

List segments:

```sh
curl -X GET "https://api.rasket.com/segments" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/segments", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/segments",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

print(response.json())
```

#### Response `200`

```json
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "id": "78261eea-8f8b-4381-83c6-79fa7120f1cf",
      "name": "Trial accounts",
      "created_at": "2026-09-08T22:22:17.595Z"
    }
  ]
}
```

- List items carry no `filter`; retrieve a segment for it.

### `GET /segments/{segment}`

One segment, with its filter.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `segment` (required) | string | The segment's ID. |

Retrieve a segment:

```sh
curl -X GET "https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

id = response.json()["id"]
```

#### Response `200`

```json
{
  "object": "segment",
  "id": "78261eea-8f8b-4381-83c6-79fa7120f1cf",
  "name": "Trial accounts",
  "filter": {
    "all": [
      {
        "field": "properties.plan",
        "op": "eq",
        "value": "trial"
      },
      {
        "field": "created_at",
        "op": "gte",
        "value": "2026-09-01T00:00:00Z"
      }
    ]
  },
  "created_at": "2026-09-08T22:22:17.595Z"
}
```

- `filter` is `null` for a segment with explicit membership only.

### `PATCH /segments/{segment}`

Change the name. The filter is fixed.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `segment` (required) | string | The segment's ID. |

#### Body

| Field | Type | Description |
| --- | --- | --- |
| `name` (required) | string | The new name. |

Rename a segment:

```sh
curl -X PATCH "https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Trials, September"
}'
```

```ts
const response = await fetch("https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Trials, September"
  }),
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.patch(
    "https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
    json={
    "name": "Trials, September"
  },
)

id = response.json()["id"]
```

#### Response `200`

```json
{
  "object": "segment",
  "id": "78261eea-8f8b-4381-83c6-79fa7120f1cf"
}
```

- A `filter` in the body is `422 invalid_parameter` rather than silently dropped. A different audience is a new segment, so that a sent broadcast keeps naming the audience it went to.

### `DELETE /segments/{segment}`

Retire the segment and free its name.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `segment` (required) | string | The segment's ID. |

Delete a segment:

```sh
curl -X DELETE "https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.delete(
    "https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

id = response.json()["id"]
```

#### Response `200`

```json
{
  "object": "segment",
  "id": "78261eea-8f8b-4381-83c6-79fa7120f1cf",
  "deleted": true
}
```

- The segment is `404` from that moment. Past broadcasts that went to it keep their recipients; a draft that names it cannot be sent until it names another.

### `GET /segments/{segment}/contacts`

The members, evaluated now: the filter's matches plus explicit additions.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `segment` (required) | string | The segment's ID. |

#### Query parameters

| Field | Type | Description |
| --- | --- | --- |
| `limit` | integer | How many items to return, 1–100. Defaults to 20. |
| `after` | string | Return the page that follows this item ID. Mutually exclusive with `before`. |
| `before` | string | Return the page that precedes this item ID. Mutually exclusive with `after`. |

List a segment's contacts:

```sh
curl -X GET "https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf/contacts" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf/contacts", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf/contacts",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

print(response.json())
```

#### Response `200`

```json
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "id": "e169aa45-1ecf-4183-9955-b1499d5701d3",
      "email": "ronald.williams@example.com",
      "first_name": "Ronald",
      "last_name": "Williams",
      "created_at": "2026-09-08T22:22:17.595Z",
      "unsubscribed": false
    }
  ]
}
```

- This is the audience a broadcast to the segment would resolve, before subscription, suppression and unsubscribe filtering. Deleted contacts are never listed.

### `GET /segments/{segment}/metrics`

How many contacts the segment resolves to right now.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `segment` (required) | string | The segment's ID. |

Retrieve a segment's size:

```sh
curl -X GET "https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf/metrics" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf/metrics", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/segments/78261eea-8f8b-4381-83c6-79fa7120f1cf/metrics",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

print(response.json())
```

#### Response `200`

```json
{
  "object": "segment_metrics",
  "segment_id": "78261eea-8f8b-4381-83c6-79fa7120f1cf",
  "contacts": 1240,
  "subscribed": 1197,
  "unsubscribed": 43
}
```

- One pass over the same membership `/contacts` lists, split on the global unsubscribe.

### `POST /segments/preview`

How many contacts a filter matches right now, without creating a segment.

#### Body

| Field | Type | Description |
| --- | --- | --- |
| `filter` (required) | object | A filter exactly as `POST /segments` takes it. |

Preview a segment:

```sh
curl -X POST "https://api.rasket.com/segments/preview" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "filter": {
    "all": [
      {
        "field": "properties.plan",
        "op": "eq",
        "value": "trial"
      },
      {
        "field": "created_at",
        "op": "gte",
        "value": "2026-09-01T00:00:00Z"
      }
    ]
  }
}'
```

```ts
const response = await fetch("https://api.rasket.com/segments/preview", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    filter: {
      all: [
        {
          field: "properties.plan",
          op: "eq",
          value: "trial"
        },
        {
          field: "created_at",
          op: "gte",
          value: "2026-09-01T00:00:00Z"
        }
      ]
    }
  }),
});

const data = await response.json();
```

```python
import os

import requests

response = requests.post(
    "https://api.rasket.com/segments/preview",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
    json={
    "filter": {
      "all": [
        {
          "field": "properties.plan",
          "op": "eq",
          "value": "trial"
        },
        {
          "field": "created_at",
          "op": "gte",
          "value": "2026-09-01T00:00:00Z"
        }
      ]
    }
  },
)

print(response.json())
```

#### Response `200`

```json
{
  "object": "segment_preview",
  "contacts": 1284
}
```

- Nothing is stored. A filter `POST /segments` would refuse is `422 validation_error` here, with the same `errors[]` paths.
