# Contact groups

> Organize customers into groups for campaigns and automations.

Source: https://tickos.dev/docs/contact-groups

Contact groups let you organize customers for targeted campaigns and automations. Members can be added by email -- if a customer with that email does not exist, one is created automatically. Group IDs start with grp_ and membership IDs with cgm_.

## GET /v1/contact-groups -- List groups

Permission: read. Returns groups newest first with { data, pagination: { limit, offset, total } }. Deleted groups are soft-deleted (is_active: false) and are still listed unless you pass is_active=true.

| Query param | Type | Default | Description |
|---|---|---|---|
| search | string | -- | Case-insensitive match on name or description. |
| is_active | boolean | -- | true for active groups only, false for deleted groups only. Omit to get both. |
| limit | number | 50 | Max 100. |
| offset | number | 0 | Offset pagination. |

## GET /v1/contact-groups/{id} -- Get a group

Permission: read. Returns { data: group } (id, account_id, name, description, color, is_active, total_contacts, metadata, created_by, created_at, updated_at). Soft-deleted groups are returned too. 404 if the group is not in your account.

## POST /v1/contact-groups -- Create a group

Permission: write. Returns 201 with { data: group, message }. Returns 400 if name is missing and 409 if a group with the same name already exists in the account (including a soft-deleted one).

| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Group name (trimmed). Unique within the account. |
| description | string | No | Description. |
| color | string | No | Hex color. Default: #16a349. |
| metadata | object | No | Arbitrary JSON. Default: {}. |

## PATCH /v1/contact-groups/{id} -- Update a group

Permission: write. Accepts name, description, color, is_active and metadata; send only the fields you want to change. An empty name is ignored. Setting is_active to true restores a deleted group. Returns { data: group, message }. Returns 404 if the group is not in your account and 409 if another group already has the new name.

## DELETE /v1/contact-groups/{id} -- Delete a group

Permission: write. Soft-deletes the group (sets is_active to false); members are kept. Returns { message }. 404 if the group is not in your account.

## GET /v1/contact-groups/{id}/members -- List members

Permission: read. Returns memberships newest first with { data, pagination: { limit, offset, total } }. Each item is a membership row (its id is the memberId used to remove it) with the customer embedded as customer: { id, email, phone, name, avatar_url, created_at }.

| Query param | Type | Default | Description |
|---|---|---|---|
| search | string | -- | Case-insensitive match on the customer email or name. |
| limit | number | 50 | Max 100. |
| offset | number | 0 | Offset pagination. |

## POST /v1/contact-groups/{id}/members -- Add members

Permission: write. Send either emails or contacts (if both are sent, contacts wins). For each contact: looks up the customer by email in your account or creates it, then adds it to the group. Existing customers are never overwritten: name and phone are only filled in when they are empty (or the name is still the email placeholder).

| Field | Type | Required | Description |
|---|---|---|---|
| emails | string[] | Conditional | Emails to add. Required if contacts is not sent. |
| contacts | array | Conditional | Array of { email, name?, phone? }. Required if emails is not sent. New customers get this name (default: the part of the email before @) and phone. |
| source | string | No | Stored as customer.source when a new customer is created: api, newsletter, form, import or manual. Default: api. Any other value returns 400. |
| origin | string | No | Free text (max 100 characters) saved on the membership as metadata.origin, e.g. "landing-page". |

```bash
curl -X POST https://api.tickos.dev/v1/contact-groups/{id}/members \
  -H 'Authorization: Bearer sk_your_api_key' \
  -H 'Content-Type: application/json' \
  -d '{
    "contacts": [
      { "email": "jane@example.com", "name": "Jane Doe" },
      { "email": "john@example.com", "phone": "+15551234567" }
    ],
    "source": "newsletter",
    "origin": "landing-page"
  }'
```

200 OK:

```json
{
  "data": {
    "added": 1,
    "skipped": 1,
    "errors": [ { "email": "not-an-email", "reason": "Invalid email format" } ]
  },
  "message": "Successfully added 1 contacts"
}
```

- added -- new members created in the group.
- skipped -- contacts that were already members (or empty emails).
- errors -- per-email failures with a reason (invalid email format, failed to create customer, failed to add to group).
- Emails are trimmed and lowercased. Returns 400 if neither emails nor contacts has at least one entry, and 404 if the group is not in your account.
- Each newly added member fires the "Contact added to group" automations configured for this group.

## DELETE /v1/contact-groups/{id}/members/{memberId}

Permission: write. Permanently removes the member from the group (the customer itself is not deleted). memberId is the membership id (cgm_...) returned by GET members, not the customer id. Returns { message }, or 404 if the group or the membership is not found.
