# Segments

> Create, count and archive segments from your server, and upload the identifier lists they match.

Source: https://docs.grovs.io/docs/api-reference/segments

A segment is a saved set of conditions, such as "iOS users who bought `pro_yearly` in the last 90 days". Use it as the audience of a [server push](https://docs.grovs.io/docs/api-reference/server-push), an [in-app message](https://docs.grovs.io/docs/api-reference/in-app-messages) or a journey. The concepts are in the [Segments guide](https://docs.grovs.io/docs/analytics/segments).

These endpoints live on the SDK host: `https://sdk.sqd.link` on grovs Cloud, `https://sdk.<SERVER_HOST>` when you self-host. Send your [project key](https://docs.grovs.io/docs/api-reference/authentication) in the `PROJECT-KEY` header. Send `production` or `test` in the `ENVIRONMENT` header. Each environment has its own segments.

**Availability:** an active paid plan on grovs Cloud. Always on for self-hosted Enterprise. Without it, every call answers `403` with `error_code: "enterprise_required"`. Estimates, counts and identifier lists need ClickHouse.

**Rate limit:** 120 requests a minute per project key, shared with [in-app messages](https://docs.grovs.io/docs/api-reference/in-app-messages). Over it, the answer is `429` with `error_code: "rate_limited"` and a `Retry-After` header.

**Dashboard only:** the member list, breakdowns, CSV exports, size history and the identifier list preview and download.

**IDs:** segment IDs are hashids, such as `gk3x`.

**Endpoints**

- `GET /api/v1/sdk/server/segments`
- `GET /api/v1/sdk/server/segments/fields`
- `POST /api/v1/sdk/server/segments/estimate`
- `POST /api/v1/sdk/server/segments`
- `GET /api/v1/sdk/server/segments/{segment_id}`
- `PATCH /api/v1/sdk/server/segments/{segment_id}`
- `DELETE /api/v1/sdk/server/segments/{segment_id}`
- `POST /api/v1/sdk/server/segments/{segment_id}/count`
- `POST /api/v1/sdk/server/segments/{segment_id}/identifier_list`

## The segment object

**Attributes**

- `id` (string): Hashid of the segment.
- `name` (string): Up to 255 characters.
- `description` (string | null): Free text.
- `definition` (object): The conditions, in canonical form: keys sorted and defaults filled in. See [The definition](#the-definition).
- `definition_hash` (string): Digest of the canonical definition. It changes when the definition does.
- `archived` (boolean): Archived segments are not evaluated and cannot be targeted.
- `has_identifier_list` (boolean): `true` once an identifier list was uploaded.
- `identifier_list` (object | null): `count` and `updated_at` of the uploaded list. `null` without one.
- `size` (object | null): The latest daily size snapshot for the current definition: `date`, `persons`, `devices`. `null` when there is none from the last 7 days, such as for a new or just edited segment.
- `created_at` (string): ISO 8601.
- `updated_at` (string): ISO 8601.

**The segment object**

```json
{
  "id": "gk3x",
  "name": "iOS pro buyers",
  "description": "Bought pro_yearly in the last 90 days",
  "definition": {
    "version": 1,
    "operator": "and",
    "groups": [
      {
        "operator": "and",
        "conditions": [
          { "type": "device", "field": "platform", "op": "is", "value": ["ios"], "mode": "current" },
          { "type": "purchase", "product_id": ["pro_yearly"], "count": 1, "window": { "last_days": 90 } }
        ]
      }
    ]
  },
  "definition_hash": "a1b2c3d4…",
  "archived": false,
  "has_identifier_list": false,
  "identifier_list": null,
  "size": { "date": "2026-10-21", "persons": 4821, "devices": 5310 },
  "created_at": "2026-10-01T10:00:00.000Z",
  "updated_at": "2026-10-21T04:00:12.000Z"
}
```

## The definition

A definition has two levels. Conditions sit inside **groups**, and each group's `operator` (`and` or `or`) combines its conditions. The top-level `operator` combines the groups. Groups cannot be nested.

**Definition**

- `version` (integer, required): Always `1`.
- `operator` (enum, required): `and` or `or`. Combines the groups.
- `groups` (array, required): 1 to 5 groups. Each has an `operator` and 1 to 10 `conditions`.

Each condition has a `type` and fields that depend on it. Every field, operator and example is in [Condition types](https://docs.grovs.io/docs/analytics/segments/condition-types).

| `type`        | Matches                                             | Key fields                                                                                        |
| ------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `event`       | A built-in event, a custom event, or any activity   | One of `event_type`, `event_name`, `any: true`. `count`, `window`, `filters`, `negate`            |
| `device`      | Device and app fields                               | `field`, `op`, `value`, `mode` (`current` or `used` with a `window`)                              |
| `profile`     | A property your app set on the user                 | `field` (`properties.<key>`), `op`, `value`                                                       |
| `first_seen`  | When the person first appeared                      | `op` (`gte` or `lte`), `value` (a date)                                                           |
| `identifier`  | Your own user ID, or the uploaded list              | `op` (`is`, `contains`, `is_set`, `is_not_set`, `in_list`, `not_in_list`), `value`                |
| `acquisition` | The install or reinstall that brought the person in | `field` (`link_id`, `campaign_id`, `tracking_source`, `tracking_medium`), `op`, `value`, `window` |
| `purchase`    | In-app purchases and subscriptions                  | `product_id`, `purchase_type`, `store_source`, `count`, `min_total_usd_cents`, `window`, `negate` |

A window is `{ "last_days": N }` or `{ "from": "YYYY-MM-DD", "to": "YYYY-MM-DD" }`, in UTC and whole days.

A "did not" condition (`negate`, `is_not`, `is_not_set`, `not_in_list`) must sit in an `and` group with a positive one.

| Limit                                | Value     |
| ------------------------------------ | --------- |
| Groups per definition                | 5         |
| Conditions per group                 | 10        |
| Filters per `event` condition        | 25        |
| Values per condition or filter       | 100       |
| Value length                         | 256 bytes |
| Identifiers per uploaded list        | 200,000   |
| `event` and `device` windows         | 90 days   |
| `acquisition` and `purchase` windows | 365 days  |

`purchase` conditions need [revenue tracking](https://docs.grovs.io/docs/revenue). Read the limits your install runs with from [the condition catalogue](#get-the-condition-catalogue).

**A definition**

```json
{
  "version": 1,
  "operator": "or",
  "groups": [
    {
      "operator": "and",
      "conditions": [
        { "type": "event", "event_type": "app_open", "window": { "last_days": 30 } },
        { "type": "event", "event_name": "checkout", "negate": true, "window": { "last_days": 30 } }
      ]
    },
    {
      "operator": "and",
      "conditions": [
        { "type": "identifier", "op": "in_list" }
      ]
    }
  ]
}
```

## List segments

`GET /api/v1/sdk/server/segments`

Returns up to 200 segments of the environment, most recently updated first.

Auth: Project key. See [Authentication](https://docs.grovs.io/docs/api-reference/authentication).

**Headers**

- `PROJECT-KEY` (string, required): Your project key.
- `ENVIRONMENT` (string, required): `production` or `test`.

**Query**

- `archived` (boolean): `true` lists archived segments instead of active ones. Default `false`.

**Returns** `200` with `segments`, a list of [segment objects](#the-segment-object), and `truncated`: `true` when there are more than 200.

**Errors**

| Status | Meaning                                                        |
| ------ | -------------------------------------------------------------- |
| 400    | Missing PROJECT-KEY, or ENVIRONMENT is not production or test. |
| 403    | Invalid credentials, or enterprise_required.                  |
| 429    | rate_limited: over 120 requests a minute for this key.        |

**Request**

```bash
curl -X GET "https://sdk.sqd.link/api/v1/sdk/server/segments" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production"
```

**Response 200 OK**

```json
{
  "segments": [
    {
      "id": "gk3x",
      "name": "iOS pro buyers",
      "description": null,
      "definition": { "version": 1, "operator": "and", "groups": ["…"] },
      "definition_hash": "a1b2c3d4…",
      "archived": false,
      "has_identifier_list": false,
      "identifier_list": null,
      "size": { "date": "2026-10-21", "persons": 4821, "devices": 5310 },
      "created_at": "2026-10-01T10:00:00.000Z",
      "updated_at": "2026-10-21T04:00:12.000Z"
    }
  ],
  "truncated": false
}
```

## Get the condition catalogue

`GET /api/v1/sdk/server/segments/fields`

Returns every condition type with its fields, operators and window caps, plus the limits this install runs with. Use it to build or validate definitions.

Auth: Project key. See [Authentication](https://docs.grovs.io/docs/api-reference/authentication).

**Headers**

- `PROJECT-KEY` (string, required): Your project key.
- `ENVIRONMENT` (string, required): `production` or `test`.

**Returns** `200` with `version`, `limits` and `conditions`. `profile` lists up to 200 `property_keys` seen on your users. `purchase` has `enabled: false` while revenue tracking is off.

**Errors**

| Status | Meaning                                                        |
| ------ | -------------------------------------------------------------- |
| 400    | Missing PROJECT-KEY, or ENVIRONMENT is not production or test. |
| 403    | Invalid credentials, or enterprise_required.                  |

**Request**

```bash
curl -X GET "https://sdk.sqd.link/api/v1/sdk/server/segments/fields" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production"
```

**Response 200 OK**

```json
{
  "version": 1,
  "limits": {
    "groups": 5,
    "conditions_per_group": 10,
    "filters": 25,
    "values": 100,
    "value_bytes": 256,
    "identifier_list": 200000
  },
  "conditions": [
    {
      "type": "event",
      "event_types": ["app_open", "view", "open", "install", "…"],
      "filter_columns": ["screen_name", "platform", "app_version", "…"],
      "filter_ops": ["is", "is_not", "contains"],
      "property_prefix": "properties.",
      "max_window_days": 90,
      "negatable": true
    },
    { "type": "profile", "ops": ["is", "is_not", "contains", "is_set", "is_not_set"], "property_prefix": "properties.", "property_keys": ["plan", "clinic_id"] },
    { "type": "purchase", "enabled": true, "purchase_types": ["subscription", "one_time"], "max_window_days": 365, "negatable": true },
    "…"
  ]
}
```

## Estimate a segment

`POST /api/v1/sdk/server/segments/estimate`

Counts the people and devices a definition matches without saving it. Send a draft `definition`, a saved `segment_id`, or both to estimate an edit of that segment against its uploaded identifier list.

Auth: Project key. See [Authentication](https://docs.grovs.io/docs/api-reference/authentication).

**Headers**

- `PROJECT-KEY` (string, required): Your project key.
- `ENVIRONMENT` (string, required): `production` or `test`.

**Body**

- `definition` (object): A [definition](#the-definition). Required without `segment_id`.
- `segment_id` (string): An active segment. Alone, it estimates that segment. With `definition`, its identifier list backs the draft's `in_list` and `not_in_list`.

**Returns** `200` with `persons`, `devices`, `approximate` (always `false`: counts are exact) and `incomplete`. `incomplete` is `true` when a window reaches past your data retention, so the count may be low.

**Errors**

| Status | Meaning                                                                                        |
| ------ | ---------------------------------------------------------------------------------------------- |
| 404    | Segment not found: unknown or archived segment_id.                                            |
| 422    | The definition is invalid, or window_expired, or query_too_heavy.                           |
| 429    | segment_busy: too many segment queries are running, retry in a few seconds. Or rate_limited. |
| 503    | clickhouse_unavailable.                                                                       |

**Request**

```bash
curl -X POST "https://sdk.sqd.link/api/v1/sdk/server/segments/estimate" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production" \
  -H "Content-Type: application/json" \
  -d '{
  "definition": {
    "version": 1,
    "operator": "or",
    "groups": [
      {
        "operator": "and",
        "conditions": [
          {
            "type": "event",
            "event_name": "checkout",
            "window": { "last_days": 30 }
          }
        ]
      }
    ]
  }
}'
```

**Response 200 OK**

```json
{
  "persons": 1204,
  "devices": 1388,
  "approximate": false,
  "incomplete": false
}
```

**Response 422 Unprocessable Entity**

```json
{
  "error": "groups: between 1 and 5 groups are required"
}
```

## Create a segment

`POST /api/v1/sdk/server/segments`

Saves a segment. The definition is validated and stored in canonical form.

Auth: Project key. See [Authentication](https://docs.grovs.io/docs/api-reference/authentication).

**Headers**

- `PROJECT-KEY` (string, required): Your project key.
- `ENVIRONMENT` (string, required): `production` or `test`.

**Body**

- `name` (string, required): Up to 255 characters.
- `definition` (object, required): A [definition](#the-definition). A fixed window that starts before your data retention cutoff is refused.
- `description` (string): Free text.

**Returns** `201` with `segment`, a [segment object](#the-segment-object).

**Errors**

| Status | Meaning                                                                                             |
| ------ | --------------------------------------------------------------------------------------------------- |
| 400    | Missing PROJECT-KEY, or ENVIRONMENT is not production or test.                                      |
| 403    | Invalid credentials, or enterprise_required.                                                       |
| 422    | definition must be an object, or the name or definition is invalid. error names the condition path. |
| 429    | rate_limited.                                                                                      |

**Request**

```bash
curl -X POST "https://sdk.sqd.link/api/v1/sdk/server/segments" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "iOS pro buyers",
  "description": "Bought pro_yearly in the last 90 days",
  "definition": {
    "version": 1,
    "operator": "and",
    "groups": [
      {
        "operator": "and",
        "conditions": [
          {
            "type": "device",
            "field": "platform",
            "op": "is",
            "value": ["ios"],
            "mode": "current"
          },
          {
            "type": "purchase",
            "product_id": ["pro_yearly"],
            "window": { "last_days": 90 }
          }
        ]
      }
    ]
  }
}'
```

**Response 201 Created**

```json
{
  "segment": {
    "id": "gk3x",
    "name": "iOS pro buyers",
    "description": "Bought pro_yearly in the last 90 days",
    "definition": { "version": 1, "operator": "and", "groups": ["…"] },
    "definition_hash": "a1b2c3d4…",
    "archived": false,
    "has_identifier_list": false,
    "identifier_list": null,
    "size": null,
    "created_at": "2026-10-22T09:00:00.000Z",
    "updated_at": "2026-10-22T09:00:00.000Z"
  }
}
```

**Response 422 Unprocessable Entity**

```json
{
  "error": "Definition groups[0].conditions[0]: type must be one of event, device, profile, first_seen, identifier, acquisition, purchase"
}
```

## Retrieve a segment

`GET /api/v1/sdk/server/segments/{segment_id}`

Returns one segment, archived or not.

Auth: Project key. See [Authentication](https://docs.grovs.io/docs/api-reference/authentication).

**Headers**

- `PROJECT-KEY` (string, required): Your project key.
- `ENVIRONMENT` (string, required): `production` or `test`.

**Path**

- `segment_id` (string, required): The segment's `id`.

**Returns** `200` with `segment`, a [segment object](#the-segment-object).

**Errors**

| Status | Meaning                                                    |
| ------ | ---------------------------------------------------------- |
| 403    | Invalid credentials, or enterprise_required.              |
| 404    | Segment not found, or it belongs to the other environment. |

**Request**

```bash
curl -X GET "https://sdk.sqd.link/api/v1/sdk/server/segments/{segment_id}" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production"
```

**Response 200 OK**

```json
{
  "segment": {
    "id": "gk3x",
    "name": "iOS pro buyers",
    "archived": false,
    "size": { "date": "2026-10-21", "persons": 4821, "devices": 5310 },
    "…": "…"
  }
}
```

**Response 404 Not Found**

```json
{
  "error": "Segment not found"
}
```

## Update a segment

`PATCH /api/v1/sdk/server/segments/{segment_id}`

Changes only the fields you send. A new definition gets a new `definition_hash` and starts a fresh size history. Campaigns already launched on the segment keep the audience they froze at launch.

Auth: Project key. See [Authentication](https://docs.grovs.io/docs/api-reference/authentication).

**Headers**

- `PROJECT-KEY` (string, required): Your project key.
- `ENVIRONMENT` (string, required): `production` or `test`.

**Path**

- `segment_id` (string, required): The segment's `id`.

**Body**

- `name` (string): Up to 255 characters.
- `description` (string | null): Free text.
- `definition` (object): A full [definition](#the-definition). It replaces the old one.
- `archived` (boolean): `false` restores an archived segment.

**Returns** `200` with `segment`, a [segment object](#the-segment-object).

**Errors**

| Status | Meaning                                                             |
| ------ | ------------------------------------------------------------------- |
| 404    | Segment not found.                                                  |
| 422    | definition must be an object, or the name or definition is invalid. |
| 429    | rate_limited.                                                      |

**Request**

```bash
curl -X PATCH "https://sdk.sqd.link/api/v1/sdk/server/segments/{segment_id}" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "iOS pro buyers, 30 days",
  "definition": {
    "version": 1,
    "operator": "and",
    "groups": [
      {
        "operator": "and",
        "conditions": [
          {
            "type": "device",
            "field": "platform",
            "op": "is",
            "value": ["ios"],
            "mode": "current"
          },
          {
            "type": "purchase",
            "product_id": ["pro_yearly"],
            "window": { "last_days": 30 }
          }
        ]
      }
    ]
  }
}'
```

**Response 200 OK**

```json
{
  "segment": {
    "id": "gk3x",
    "name": "iOS pro buyers, 30 days",
    "definition_hash": "f7e6d5c4…",
    "size": null,
    "…": "…"
  }
}
```

## Archive a segment

`DELETE /api/v1/sdk/server/segments/{segment_id}`

Archives the segment. It is not deleted: it stops being evaluated, drops out of the list and cannot be targeted. Restore it with [Update a segment](#update-a-segment) and `archived: false`.

Auth: Project key. See [Authentication](https://docs.grovs.io/docs/api-reference/authentication).

**Headers**

- `PROJECT-KEY` (string, required): Your project key.
- `ENVIRONMENT` (string, required): `production` or `test`.

**Path**

- `segment_id` (string, required): The segment's `id`.

**Returns** `200` with a `message`.

**Errors**

| Status | Meaning            |
| ------ | ------------------ |
| 404    | Segment not found. |
| 429    | rate_limited.     |

**Request**

```bash
curl -X DELETE "https://sdk.sqd.link/api/v1/sdk/server/segments/{segment_id}" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production"
```

**Response 200 OK**

```json
{
  "message": "Segment archived"
}
```

## Count a segment

`POST /api/v1/sdk/server/segments/{segment_id}/count`

Returns the exact number of people and devices in a saved segment. The first count after a change can take a few seconds. The result is reused for a few minutes, so repeat calls are fast.

Server counts run in their own pool, apart from the dashboard's.

Auth: Project key. See [Authentication](https://docs.grovs.io/docs/api-reference/authentication).

**Headers**

- `PROJECT-KEY` (string, required): Your project key.
- `ENVIRONMENT` (string, required): `production` or `test`.

**Path**

- `segment_id` (string, required): An active segment's `id`.

**Returns** `200` with `persons`, `devices`, `incomplete` and `result_id`, the ID of the computed result.

**Errors**

| Status | Meaning                                                    |
| ------ | ---------------------------------------------------------- |
| 404    | Segment not found, or it is archived.                      |
| 422    | invalid_definition, window_expired or query_too_heavy. |
| 429    | segment_busy: retry in a few seconds. Or rate_limited.   |
| 503    | clickhouse_unavailable.                                   |

**Request**

```bash
curl -X POST "https://sdk.sqd.link/api/v1/sdk/server/segments/{segment_id}/count" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production"
```

**Response 200 OK**

```json
{
  "persons": 4821,
  "devices": 5310,
  "incomplete": false,
  "result_id": "3f9a1c2e…"
}
```

**Response 429 Too Many Requests**

```json
{
  "error": "Too many segment queries are running; retry shortly.",
  "error_code": "segment_busy"
}
```

## Replace the identifier list

`POST /api/v1/sdk/server/segments/{segment_id}/identifier_list`

Stores a list of your user IDs on the segment, replacing any earlier list. Match it with an `identifier` condition and `op: "in_list"` or `"not_in_list"`. Use it when your server knows who belongs, such as members of a loyalty tier.

Identifiers are stored exactly as sent and compared to the user ID your app sets through the SDK. A failed upload keeps the previous list.

Auth: Project key. See [Authentication](https://docs.grovs.io/docs/api-reference/authentication).

**Headers**

- `PROJECT-KEY` (string, required): Your project key.
- `ENVIRONMENT` (string, required): `production` or `test`.

**Path**

- `segment_id` (string, required): An active segment's `id`.

**Body**

- `identifiers` (string\[], required): 1 to 200,000 strings of 1 to 256 bytes. Duplicates count once.

**Returns** `200` with the updated `segment` and `count`, the number of distinct identifiers stored.

**Errors**

| Status | Meaning                                                                                            |
| ------ | -------------------------------------------------------------------------------------------------- |
| 404    | Segment not found, or it is archived.                                                              |
| 409    | upload_in_progress: another upload for this segment is still running.                            |
| 422    | The list is empty, over 200,000 entries, or has an identifier that is not a string of 1-256 bytes. |
| 503    | clickhouse_unavailable.                                                                           |

**Request**

```bash
curl -X POST "https://sdk.sqd.link/api/v1/sdk/server/segments/{segment_id}/identifier_list" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production" \
  -H "Content-Type: application/json" \
  -d '{
  "identifiers": ["member-4471", "member-9021", "member-4471"]
}'
```

**Response 200 OK**

```json
{
  "segment": {
    "id": "gk3x",
    "has_identifier_list": true,
    "identifier_list": { "count": 2, "updated_at": "2026-10-22T09:14:03.120Z" },
    "…": "…"
  },
  "count": 2
}
```

**Response 422 Unprocessable Entity**

```json
{
  "error": "the list needs between 1 and 200000 identifiers"
}
```
