> ## Documentation Index
> Fetch the complete documentation index at: https://docs.omneo.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Working with Groups

> Create Group Definitions, manage static membership, and read members across every group type via the Omneo API.

A **Group** is a named set of Profiles you target for messaging, personalisation, and integration. You create a **Group Definition** once, choose its `type`, then manage membership according to that type. This guide covers creating a definition, running a static group end to end, and reading members for any group. For the model behind groups, see the [Groups concept](/concepts/groups).

All endpoints are bearer authenticated and live under `https://api.[tenant].getomneo.com/api/v3`.

## Choose a group type

Every Group Definition has one of three types. The type sets how membership is determined and which endpoints you use to manage it.

| Type        | Members determined by                         | Use when                                                                                                                                                                                   |
| ----------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `static`    | Explicit add and remove calls                 | You maintain the list yourself: hand-curated segments, imported lists, or membership another system owns.                                                                                  |
| `dynamic`   | Rules evaluated on a refresh schedule         | Membership should follow Profile data automatically, for example "profiles that spent over \$500 in the last 12 months". See [Building dynamic groups](/dev-guides/groups/dynamic-groups). |
| `favourite` | Resolved from the owner Profile's connections | Each Profile has its own set, for example a customer's favourite stores or preferred stylists. See [Favourite groups](/dev-guides/groups/favourite-groups).                                |

## Create a Group Definition

Create a definition with `POST /groups/definitions`. Only `name`, `handle`, and `type` are required.

```shell theme={null}
curl -X POST "https://api.[tenant].getomneo.com/api/v3/groups/definitions" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "VIP customers",
    "handle": "vip-customers",
    "type": "static"
  }'
```

The response is `201` with the created definition, including its `id`, which you use in the membership endpoints below.

| Field                                        | Required | Description                                 |
| -------------------------------------------- | -------- | ------------------------------------------- |
| `name`                                       | Yes      | Human-readable name.                        |
| `handle`                                     | Yes      | Handle for the definition.                  |
| `type`                                       | Yes      | `static`, `dynamic`, or `favourite`.        |
| `short_description` / `description`          | No       | Descriptive copy shown alongside the group. |
| `meta`                                       | No       | Free-form metadata object.                  |
| `is_published` / `is_active` / `is_archived` | No       | Publication and lifecycle flags.            |

Dynamic groups add query and rule fields; favourite groups add `source_type` and `source_id`. Those are covered on the sibling pages.

<Note>
  The `type` is set when the definition is created and cannot be changed afterwards. To move membership to a different mechanism, create a new definition of the type you want.
</Note>

## Read, update, and delete a definition

Read a single definition with `GET /groups/definitions/{group}`, change it with `PUT`, and remove it with `DELETE`. `PUT` accepts the same fields as create, except `type`, which is fixed once set. `DELETE` returns `204` and removes the definition together with its membership records.

```shell theme={null}
# Read one definition
curl "https://api.[tenant].getomneo.com/api/v3/groups/definitions/{group}" \
  -H "Authorization: Bearer ${TOKEN}"

# Update a definition
curl -X PUT "https://api.[tenant].getomneo.com/api/v3/groups/definitions/{group}" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{ "short_description": "Hand-curated VIP list" }'

# Delete a definition
curl -X DELETE "https://api.[tenant].getomneo.com/api/v3/groups/definitions/{group}" \
  -H "Authorization: Bearer ${TOKEN}"
```

A read returns the full definition. Every definition returns the same field set regardless of `type`; fields that do not apply to the group's type are `null` or empty. The response below is the `static` group created above.

```json theme={null}
{
  "data": {
    "id": 17,
    "name": "VIP customers",
    "handle": "vip-customers",
    "type": "static",
    "source_type": null,
    "source_id": null,
    "query": null,
    "query_type": null,
    "arguments": null,
    "period": 30,
    "period_type": "days",
    "absolute_expiry": null,
    "refresh_period": null,
    "refresh_period_type": null,
    "refresh_absolute_expiry": null,
    "last_refreshed_at": null,
    "next_refresh_at": null,
    "refresh_status": null,
    "refresh_error": null,
    "short_description": "Hand-curated VIP list",
    "description": null,
    "owner_profile_id": null,
    "meta": {},
    "is_published": true,
    "is_active": true,
    "is_archived": false,
    "current_member_count": 128,
    "rules": [],
    "created_at": "2026-07-01T09:00:00Z",
    "updated_at": "2026-07-10T14:30:00Z"
  }
}
```

| Field                                                                | Applies to | Description                                                                                                   |
| -------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------- |
| `id`                                                                 | All        | Definition id used in the membership, rule, and snapshot endpoints.                                           |
| `name`                                                               | All        | Human-readable name.                                                                                          |
| `handle`                                                             | All        | Handle for the definition.                                                                                    |
| `type`                                                               | All        | `static`, `dynamic`, or `favourite`. Fixed once set.                                                          |
| `source_type` / `source_id`                                          | Favourite  | Member source. Currently `connection_definition` and the Connection Definition id. `null` otherwise.          |
| `query` / `query_type` / `arguments`                                 | Dynamic    | Candidate query name, its type, and its `[{name,value}]` arguments. `null` otherwise.                         |
| `period` / `period_type` / `absolute_expiry`                         | Static     | Membership expiry: a `period` counted in `period_type` units, or a fixed `absolute_expiry` date.              |
| `refresh_period` / `refresh_period_type` / `refresh_absolute_expiry` | Dynamic    | Re-evaluation cadence.                                                                                        |
| `last_refreshed_at` / `next_refresh_at`                              | Dynamic    | When the group last re-evaluated and when it is next due.                                                     |
| `refresh_status`                                                     | Dynamic    | Evaluation state: `fresh`, `stale`, `refreshing`, or `failed`.                                                |
| `refresh_error`                                                      | Dynamic    | Message from the last failed run, otherwise `null`.                                                           |
| `short_description` / `description`                                  | All        | Descriptive copy shown alongside the group.                                                                   |
| `owner_profile_id`                                                   | Favourite  | The owning Profile on a profile-owned instance. `null` on a shared definition.                                |
| `meta`                                                               | All        | Free-form metadata object.                                                                                    |
| `is_published` / `is_active` / `is_archived`                         | All        | Publication and lifecycle flags.                                                                              |
| `current_member_count`                                               | Static     | Count of active static members. Always `0` for dynamic and favourite.                                         |
| `rules`                                                              | Dynamic    | The attached inclusion and exclusion rules. See [Building dynamic groups](/dev-guides/groups/dynamic-groups). |
| `created_at` / `updated_at`                                          | All        | Timestamps.                                                                                                   |

## Run a static group

A static group is a list you manage directly. Create a `static` definition, then add and remove Profiles.

<Steps>
  <Step title="Add a Profile">
    ```shell theme={null}
    curl -X POST "https://api.[tenant].getomneo.com/api/v3/groups/definitions/{group}/profiles" \
      -H "Authorization: Bearer ${TOKEN}" \
      -H "Content-Type: application/json" \
      -d '{ "profile_id": "a1b460b2-953f-4587-b4eb-fb0f29b55e02" }'
    ```

    The add is idempotent. A Profile that is already an active member returns `200`; a newly added Profile returns `201`.

    | Field                       | Required | Description                                            |
    | --------------------------- | -------- | ------------------------------------------------------ |
    | `profile_id`                | Yes      | The Profile to add.                                    |
    | `source_type` / `source_id` | No       | Where the membership came from, for your own tracking. |
    | `added_by_profile_id`       | No       | The Profile that performed the add.                    |
    | `meta`                      | No       | Free-form metadata stored on the membership.           |
  </Step>

  <Step title="Remove a Profile">
    ```shell theme={null}
    curl -X POST "https://api.[tenant].getomneo.com/api/v3/groups/definitions/{group}/profiles/{profile}/remove" \
      -H "Authorization: Bearer ${TOKEN}"
    ```

    Removal returns `404` when the Profile has no active membership in the group, so a `404` here means there was nothing to remove.
  </Step>
</Steps>

<Info>
  Direct add and remove apply to `static` groups only. Dynamic membership is computed from rules, and favourite membership is managed per Profile through the profile-scoped endpoints.
</Info>

### Expire memberships

To make static memberships expire, set `period` and `period_type` on the definition, or set an `absolute_expiry` date. Each membership's expiry is calculated once, at the moment the Profile is added.

```shell theme={null}
curl -X POST "https://api.[tenant].getomneo.com/api/v3/groups/definitions" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Summer promo",
    "handle": "summer-promo",
    "type": "static",
    "period": 30,
    "period_type": "days"
  }'
```

`period_type` accepts `days`, `weeks`, `months`, `years`, `absolute_date`, `absolute_week`, or `absolute_month`. Use `absolute_expiry` when every membership should end on the same fixed date rather than a period counted from when each Profile joined.

## Read members

List a group's members with `GET /groups/definitions/{group}/profiles`. One endpoint returns members for every group type in the same shape, so consumers do not branch on `type`.

```shell theme={null}
curl "https://api.[tenant].getomneo.com/api/v3/groups/definitions/{group}/profiles" \
  -H "Authorization: Bearer ${TOKEN}"
```

| Type        | What the listing returns                                    |
| ----------- | ----------------------------------------------------------- |
| `static`    | The Profiles currently on the list.                         |
| `dynamic`   | The members from the most recent completed snapshot.        |
| `favourite` | The connected Profiles across all owners of the definition. |

Each row has the same wrapper for every type: a `profile_id`, a `record_type` that tells you which membership mechanism produced the row, and a `record` holding the underlying membership object. The example below is a `static` group, so every row is a `group_profile`.

```json theme={null}
{
  "data": [
    {
      "profile_id": "a1b460b2-953f-4587-b4eb-fb0f29b55e02",
      "record_type": "group_profile",
      "record": {
        "profile_id": "a1b460b2-953f-4587-b4eb-fb0f29b55e02",
        "source_type": "import",
        "source_id": "2026-q3-vip.csv",
        "added_by_profile_id": null,
        "meta": {},
        "created_at": "2026-07-02T11:00:00Z"
      }
    }
  ],
  "meta": { "pagination": { "total": 128, "count": 1, "per_page": 50, "current_page": 1, "total_pages": 3 } }
}
```

The `record_type` depends on the group's `type`, and `record` carries the corresponding resource.

| `record_type`            | Group type  | What `record` contains                                                                                                       |
| ------------------------ | ----------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `group_profile`          | `static`    | The static membership record.                                                                                                |
| `group_snapshot_profile` | `dynamic`   | The Profile's row from the most recent completed snapshot. See [Building dynamic groups](/dev-guides/groups/dynamic-groups). |
| `connection`             | `favourite` | The connection linking the owner Profile to the member. See [Favourite groups](/dev-guides/groups/favourite-groups).         |

The listing is paginated. Page through `meta.pagination` as with every Omneo list endpoint.

<Warning>
  The `current_member_count` on a definition counts static members only. It is always `0` for dynamic and favourite groups. To size a dynamic group, read its latest snapshot; to size a favourite group, page the member listing.
</Warning>

## Worked example: a static VIP list

This walks a static group from creation to a running, expiring list.

<Steps>
  <Step title="Create the definition">
    `POST /groups/definitions` with `type: "static"`, `period: 30`, and `period_type: "days"`. The response is `201` and includes the new `id`, for example `17`. Every membership added from now on expires 30 days after it is added.
  </Step>

  <Step title="Add members">
    `POST /groups/definitions/17/profiles` with a `profile_id` for each customer. A first add returns `201`; re-adding the same Profile returns `200` without creating a duplicate. Pass `source_type` and `source_id` to record the import or system the membership came from.
  </Step>

  <Step title="Read the current list">
    `GET /groups/definitions/17/profiles` returns `group_profile` rows and `meta.pagination`. `GET /groups/definitions/17` shows `current_member_count` for a quick size.
  </Step>

  <Step title="Remove a member">
    `POST /groups/definitions/17/profiles/{profile}/remove` ends the membership. If that Profile is not an active member, the call returns `404`, so a `404` here means there was nothing to remove.
  </Step>

  <Step title="Act on changes">
    Subscribe to `group.profile.add` and `group.profile.remove` through a [Reaction](/concepts/automation/reactions) to message customers as they join or leave, including when a membership expires.
  </Step>
</Steps>

## Errors

| Status | When                                                                                                                                                                               |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200`  | Add succeeded for a Profile that was already an active member (idempotent, no duplicate created).                                                                                  |
| `201`  | Add created a new membership, or a definition was created.                                                                                                                         |
| `204`  | A definition was deleted.                                                                                                                                                          |
| `401`  | Missing or invalid bearer token.                                                                                                                                                   |
| `403`  | The token lacks the scope or permission for this group.                                                                                                                            |
| `404`  | The Group Definition does not exist, or (on remove) the Profile has no active membership in it.                                                                                    |
| `422`  | Validation failed. For example, manual add or remove against a `dynamic` or `favourite` group, or sending `query`, rule, or `source` fields that do not apply to a `static` group. |

## Edge cases and gotchas

* **`type` is immutable.** You cannot convert a group between `static`, `dynamic`, and `favourite`. Create a new definition instead.
* **Manual membership is static only.** Add and remove calls are rejected with `422` on `dynamic` and `favourite` groups. Dynamic membership is computed from rules; favourite membership is managed per Profile.
* **Expiry is fixed at add time.** A membership's expiry is calculated once, when the Profile is added. Changing `period` or `absolute_expiry` later does not reschedule memberships already created.
* **`current_member_count` is static only.** It stays `0` for dynamic and favourite groups even when they have members. Size those from the member listing or the latest snapshot.
* **Instances are favourite only.** Creating a profile-owned instance of a `static` or `dynamic` definition returns `422`. See [Favourite groups](/dev-guides/groups/favourite-groups).

## Subscribe to group events

Membership and definition changes emit events you can act on through [Reactions](/concepts/automation/reactions) and webhook [Targets](/concepts/automation/targets).

| Event                      | Fires when                                                                                       |
| -------------------------- | ------------------------------------------------------------------------------------------------ |
| `group.profile.add`        | A Profile joins a group (static add, dynamic match, or favourite connection).                    |
| `group.profile.remove`     | A Profile leaves a group (static removal or expiry, dynamic departure, or favourite disconnect). |
| `group_definition.created` | A Group Definition is created.                                                                   |
| `group_definition.updated` | A Group Definition is changed.                                                                   |
| `group_definition.deleted` | A Group Definition is deleted.                                                                   |

For example, subscribe to `group.profile.add` to send a welcome message when a customer enters a VIP group, or to `group.profile.remove` to revoke access when they leave.

## Next steps

<Columns cols={2}>
  <Card title="Building dynamic groups" href="/dev-guides/groups/dynamic-groups">
    Compute membership from a candidate query and reusable inclusion and exclusion rules.
  </Card>

  <Card title="Favourite groups" href="/dev-guides/groups/favourite-groups">
    Give each Profile its own set, resolved from that Profile's connections.
  </Card>
</Columns>

## Related

* [Groups concept](/concepts/groups)
* [Building dynamic groups](/dev-guides/groups/dynamic-groups)
* [Favourite groups](/dev-guides/groups/favourite-groups)
* [Reactions](/concepts/automation/reactions)
* [Targets](/concepts/automation/targets)
* [Group Definition API](/api-reference/group-definition/overview)
* [Add Group Definition API](/api-reference/group-definition/add-group-definition)
* [Browse Group Definitions API](/api-reference/group-definition/browse-group-definitions)
* [Read Group Definition API](/api-reference/group-definition/read-group-definition)
* [Edit Group Definition API](/api-reference/group-definition/edit-group-definition)
* [Delete Group Definition API](/api-reference/group-definition/delete-group-definition)
* [Add a Profile to a static group API](/api-reference/group-profile/add-a-profile-to-a-static-group)
* [Remove a Profile from a static group API](/api-reference/group-profile/remove-a-profile-from-a-static-group)
