# Groups, segments and fields

> Reading needs Subscribers: view, writing Subscribers: manage.

Reading needs Subscribers: view, writing Subscribers: manage.

## Groups

| Method and path | What it does |
|---|---|
| `GET /groups` | Groups by name. `filter[name]` (name contains), `limit`, `cursor`. |
| `POST /groups` | Creates a group: `{"name": "..."}`. `201`. |
| `PUT /groups/{id}` | Renames a group: `{"name": "..."}`. `200`. |
| `DELETE /groups/{id}` | Deletes a group (its subscribers stay). `204`. |
| `GET /groups/{id}/subscribers` | The group's subscribers ([List subscribers](https://docs.uptools.io/echo/developer-guide/subscribers/#list-subscribers)). |
| `POST /groups/{id}/import-subscribers` | Bulk import into the group ([Import through the API](https://docs.uptools.io/echo/developer-guide/import/)). |

**Create a group**

```bash
curl -X POST "$ECHO_API/groups" \
  -H "Authorization: Bearer $ECHO_TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"Webshop vásárlók"}'
```

```json
{"data":{"id":"257e83a2-d55e-4a8c-b9a4-b3e939c151cc","name":"Webshop vásárlók","created_at":"2026-10-22T08:52:08.269421Z","subscribers":0,"total":0,"open_rate":0,"click_rate":0}}
```

**Search groups by name**

```bash
curl -g -H "Authorization: Bearer $ECHO_TOKEN" "$ECHO_API/groups?filter[name]=VIP"
```

```json
{"data":[{"id":"000d6529-0078-40d8-9c29-05cd541c4c2d","name":"VIP vásárlók","created_at":"2026-04-17T12:00:00Z","subscribers":466,"total":495,"open_rate":34.64,"click_rate":7.47,"timed_out":false}],"meta":{"next_cursor":null,"prev_cursor":null,"limit":25,"total":1}}
```

| Key | Meaning |
|---|---|
| `subscribers` | Active members. |
| `total` | Members in any status. |
| `open_rate`, `click_rate` | The members' average rates, in percent. |
| `timed_out` | Lists only: `true` if counting gave up, and the counts are then `null`. |

**Rename and delete**

```bash
curl -X PUT "$ECHO_API/groups/257e83a2-d55e-4a8c-b9a4-b3e939c151cc" \
  -H "Authorization: Bearer $ECHO_TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"Webshop vásárlók 2026"}'
curl -X DELETE "$ECHO_API/groups/257e83a2-d55e-4a8c-b9a4-b3e939c151cc" -H "Authorization: Bearer $ECHO_TOKEN"
```

A name already in use answers `409 name_taken`. A group that a segment, a campaign or a workflow uses can't be deleted:

```json
{"error":{"code":"in_use","message":"It's used by these segments: Visszatérő webshop vásárlók"}}
```

Deleting a group sends no `subscriber.removed_from_group` webhook events for its members.

## Segments

Segments are saved filters. Their conditions are edited in the app; the API reads them, renames and deletes them.

| Method and path | What it does |
|---|---|
| `GET /segments` | Segments by name. `filter[name]`, `limit`, `cursor`. |
| `GET /segments/{id}/subscribers` | The subscribers who match now. |
| `PUT /segments/{id}` | Renames: `{"name": "..."}`. Any other key is `422`. |
| `DELETE /segments/{id}` | Deletes. `409 in_use` if a campaign or workflow uses it. |

```bash
curl -H "Authorization: Bearer $ECHO_TOKEN" "$ECHO_API/segments?limit=1"
```

```json
{"data":[{"id":"026c692e-e106-4bb6-b971-78757cb3fd02","name":"Aktív olvasók (30 nap)","rules":{"version":1,"groups":[[{"type":"email_actions","op":"opened_or_clicked","amount":30}]]},"subscribers":2428,"open_rate":52.88,"click_rate":11.32,"counted_at":"2026-10-22T05:16:28.536739Z","created_at":"2026-05-12T08:00:00Z","updated_at":"2026-05-15T08:00:00Z","timed_out":false}],"meta":{"next_cursor":"bzox","prev_cursor":null,"limit":1,"total":6}}
```

`subscribers`, `open_rate` and `click_rate` are from the last count (`counted_at`); `timed_out` is `true` if the last count gave up and the numbers are older. `rules` is the segment's condition set as the app stores it: treat it as read-only.

```bash
curl -X PUT "$ECHO_API/segments/6010f9ca-331d-4c81-a3ab-1ed48a62a12b" \
  -H "Authorization: Bearer $ECHO_TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"Visszatérő webshop vásárlók"}'
```

```json
{"data":{"id":"6010f9ca-331d-4c81-a3ab-1ed48a62a12b","name":"Visszatérő webshop vásárlók","rules":{"version":1,"groups":[[{"type":"groups","op":"belongs_to","values":["257e83a2-d55e-4a8c-b9a4-b3e939c151cc"]}]]},"subscribers":2,"open_rate":0,"click_rate":0,"counted_at":"2026-10-22T08:52:10.230434Z","created_at":"2026-10-22T08:52:10.217956Z","updated_at":"2026-10-22T08:52:10.234278Z","timed_out":false}}
```

## Fields

Fields hold the subscribers' data. Built-in fields (`email`, `name`, `last_name`, `company`, `country`, `city`, `phone`, `state`, `zip`) always exist; custom fields are added per workspace. In subscriber bodies you use the field's `key`.

| Method and path | What it does |
|---|---|
| `GET /fields` | Built-in fields, then custom fields. Not paged. |
| `POST /fields` | Creates a custom field: `{"name": "...", "type": "text" \| "number" \| "date"}`. `201`. |
| `PUT /fields/{key}` | Renames: `{"name": "..."}`. The key and the merge tag stay. |
| `DELETE /fields/{key}` | Deletes a custom field and its value from every subscriber. `204`. |

```bash
curl -H "Authorization: Bearer $ECHO_TOKEN" "$ECHO_API/fields"
```

```json
{"data":[{"key":"email","name":"Email","type":"text","builtin":true,"tag":"{$email}","created_at":null},{"key":"name","name":"Name","type":"text","builtin":true,"tag":"{$name}","created_at":null},{"key":"last_name","name":"Last name","type":"text","builtin":true,"tag":"{$last_name}","created_at":null},{"key":"company","name":"Company","type":"text","builtin":true,"tag":"{$company}","created_at":null},{"key":"country","name":"Country","type":"text","builtin":true,"tag":"{$country}","created_at":null},{"key":"city","name":"City","type":"text","builtin":true,"tag":"{$city}","created_at":null},{"key":"phone","name":"Phone","type":"text","builtin":true,"tag":"{$phone}","created_at":null},{"key":"state","name":"State","type":"text","builtin":true,"tag":"{$state}","created_at":null},{"key":"zip","name":"Zip","type":"text","builtin":true,"tag":"{$zip}","created_at":null},{"key":"szuletesnap","name":"Születésnap","type":"date","builtin":false,"tag":"{$szuletesnap}","created_at":"2026-04-16T06:55:00Z"},{"key":"rendelesek_szama","name":"Rendelések száma","type":"number","builtin":false,"tag":"{$rendelesek_szama}","created_at":"2026-04-16T06:55:00Z"}]}
```

(The answer is shortened: the demo workspace has a few more custom fields.)

**Create, rename, delete a custom field**

```bash
curl -X POST "$ECHO_API/fields" \
  -H "Authorization: Bearer $ECHO_TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"Kedvenc növény","type":"text"}'
```

```json
{"data":{"key":"kedvenc_noveny","name":"Kedvenc növény","type":"text","builtin":false,"tag":"{$kedvenc_noveny}","created_at":"2026-10-22T08:52:10.268043Z"}}
```

The key is made from the name (lower case, accents removed, spaces to `_`). Read it from the answer instead of building it yourself.

```bash
curl -X PUT "$ECHO_API/fields/kedvenc_noveny" \
  -H "Authorization: Bearer $ECHO_TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"Kedvenc virág"}'
curl -X DELETE "$ECHO_API/fields/kedvenc_noveny" -H "Authorization: Bearer $ECHO_TOKEN"
```

| Error | When |
|---|---|
| `409 name_taken` | Another field already has this name. |
| `409 builtin_field` | You tried to rename or delete a built-in field. |

```json
{"error":{"code":"builtin_field","message":"Default fields can't be renamed or deleted."}}
```

## Time zones

`GET /timezones` lists the IANA time zone names Echo accepts (for example when [scheduling a campaign](https://docs.uptools.io/echo/developer-guide/campaigns/#schedule-and-cancel)). Any valid token can call it.

```json
{"data":[{"id":"Africa/Abidjan"},{"id":"Africa/Accra"},{"id":"Africa/Addis_Ababa"}]}
```

(Shortened.)
