# Contact lists

> Every field of /v1/lists: list, create, read, rename, page through and add contacts, remove one contact, delete.

<!-- Generated by apps/docs/scripts/gen-reference.mjs from apps/docs/public/openapi/v1.json. Do not edit: change the API's schemas (then `pnpm --filter @seasalt/api openapi:emit`) or the notes in apps/docs/src/reference-notes/, then run `pnpm --filter @seasalt/docs gen:reference`. -->

The contact lists a campaign sends to (`audience.list`), and the contacts in them. How-to: [Run a campaign](/platform-api/campaigns/#finding-ids), [Keep contacts](/platform-api/contacts/#lists).

Generated from [`/openapi/v1.json`](/openapi/v1.json); interactive version: [API reference](/openapi/v1/).

## Endpoints

| method | path | what it does | success |
|---|---|---|---|
| [`GET`](#get-v1lists) | `/v1/lists` | List contact lists, for a campaign's `audience.list` | `200` |
| [`POST`](#post-v1lists) | `/v1/lists` | Create a contact list, with its contacts | `201` |
| [`GET`](#get-v1listsid) | `/v1/lists/{id}` | Read a contact list | `200` |
| [`PATCH`](#patch-v1listsid) | `/v1/lists/{id}` | Rename a contact list | `200` |
| [`DELETE`](#delete-v1listsid) | `/v1/lists/{id}` | Delete a contact list no campaign is using | `200` |
| [`GET`](#get-v1listsidcontacts) | `/v1/lists/{id}/contacts` | List the contacts in a list | `200` |
| [`POST`](#post-v1listsidcontacts) | `/v1/lists/{id}/contacts` | Add contacts to a list | `200` |
| [`DELETE`](#delete-v1listsidcontactscontact) | `/v1/lists/{id}/contacts/{contact}` | Take one contact out of a list | `200` |

## `GET /v1/lists`

List contact lists, for a campaign's `audience.list`.

**Parameters**

<div class="ref-table">

| name | in | type | description |
|---|---|---|---|
| `limit` | query | integer<br>1–200 | Page size, 1 to 200 (default 50). |
| `after` | query | string | The previous page's `next_cursor`. |

</div>

**Answers** `200` with [ContactListList](#contactlistlist). Errors use the [error envelope](/platform-api/reference/#v1error); common codes: [contact lists](/platform-api/errors/#contact-lists), [lists](/platform-api/errors/#lists).

## `POST /v1/lists`

Create a contact list, with its contacts.

Makes a list (`source: api`) and imports `contacts` into it through the same import as a campaign's `audience.contacts`: a number a contact already has reuses that contact, never a second one. Each phone is stored for SMS, calls and WhatsApp alike, so the list works for a campaign on any channel. Storing a number is not consent: opt-outs stay on the number. `contact_ids` adds contacts you already have, by id, as `POST /v1/lists/{id}/contacts` does; an id that is not one of your live contacts is skipped and named in `import.errors`. A request that names people (`contacts` and/or `contact_ids`) and would add none of them is `400 empty_audience_input` and creates no list. Pass the list's `id` as a campaign's `audience.list`.

**Parameters**

<div class="ref-table">

| name | in | type | description |
|---|---|---|---|
| `idempotency-key` | header | string<br>≤ 255 chars | Send one with every create. A retry with the same key makes no second list: it answers the first one as it is now, with the first `import`. Rules: [Idempotency](/platform-api/retries/#idempotency). |

</div>

**Request body:** [ContactListCreate](#contactlistcreate).

**Answers** `201` with [ContactListWithImport](#contactlistwithimport). Errors use the [error envelope](/platform-api/reference/#v1error); common codes: [contact lists](/platform-api/errors/#contact-lists), [lists](/platform-api/errors/#lists).

## `GET /v1/lists/{id}`

Read a contact list.

**Parameters**

<div class="ref-table">

| name | in | type | description |
|---|---|---|---|
| `id` | path | string, required | The list's `cl-…` id or UUID. |

</div>

**Answers** `200` with [ContactList](#contactlist). Errors use the [error envelope](/platform-api/reference/#v1error); common codes: [contact lists](/platform-api/errors/#contact-lists), [lists](/platform-api/errors/#lists).

## `PATCH /v1/lists/{id}`

Rename a contact list.

Changes the list's `name`; nothing else. Any list `GET /v1/lists` shows can be renamed, whatever its `source`: a `campaign` list keeps its source and is still removed with its campaign's draft. The same name is no change. A rename is idempotent, so no `Idempotency-Key` is needed (one sent is ignored). Add contacts with `POST /v1/lists/{id}/contacts`.

**Parameters**

<div class="ref-table">

| name | in | type | description |
|---|---|---|---|
| `id` | path | string, required | The list's `cl-…` id or UUID. |

</div>

**Request body:** [ContactListUpdate](#contactlistupdate).

**Answers** `200` with [ContactList](#contactlist). Errors use the [error envelope](/platform-api/reference/#v1error); common codes: [contact lists](/platform-api/errors/#contact-lists), [lists](/platform-api/errors/#lists).

## `DELETE /v1/lists/{id}`

Delete a contact list no campaign is using.

The list disappears from `GET /v1/lists` and can no longer be an `audience.list`. Its contacts, and their opt-outs, stay. Refused while a draft, queued, running or paused campaign uses it; a finished campaign does not keep it.

**Parameters**

<div class="ref-table">

| name | in | type | description |
|---|---|---|---|
| `id` | path | string, required | The list's `cl-…` id or UUID. |

</div>

**Answers** `200` with the object below. Errors use the [error envelope](/platform-api/reference/#v1error); common codes: [contact lists](/platform-api/errors/#contact-lists), [lists](/platform-api/errors/#lists).

<div class="ref-table">

| field | type | description |
|---|---|---|
| `id` | string, required |   |
| `object` | string, required<br>always `contact_list` |   |
| `deleted` | boolean, required<br>always `true` |   |

</div>

Example: see [Run a campaign](/platform-api/campaigns/#finding-ids).

## `GET /v1/lists/{id}/contacts`

List the contacts in a list.

**Parameters**

<div class="ref-table">

| name | in | type | description |
|---|---|---|---|
| `id` | path | string, required | The list's `cl-…` id or UUID. |
| `limit` | query | integer<br>1–200 | Page size, 1 to 200 (default 50). |
| `after` | query | string | The previous page's `next_cursor`. |

</div>

**Answers** `200` with [ContactListMemberList](#contactlistmemberlist). Errors use the [error envelope](/platform-api/reference/#v1error); common codes: [contact lists](/platform-api/errors/#contact-lists), [lists](/platform-api/errors/#lists).

## `POST /v1/lists/{id}/contacts`

Add contacts to a list.

Imports `contacts` into the list, as `POST /v1/lists` does, and adds `contact_ids`, contacts you already have. Answers the list and what the import did: an id that is not one of your contacts is skipped and named in `import.errors`.

**Parameters**

<div class="ref-table">

| name | in | type | description |
|---|---|---|---|
| `id` | path | string, required | The list's `cl-…` id or UUID. |
| `idempotency-key` | header | string<br>≤ 255 chars | Send one with every add. A retry with the same key imports nothing twice: it answers the list as it is now, with the first `import`. Rules: [Idempotency](/platform-api/retries/#idempotency). |

</div>

**Request body:** [ContactListAddContacts](#contactlistaddcontacts).

**Answers** `200` with [ContactListWithImport](#contactlistwithimport). Errors use the [error envelope](/platform-api/reference/#v1error); common codes: [contact lists](/platform-api/errors/#contact-lists), [lists](/platform-api/errors/#lists).

## `DELETE /v1/lists/{id}/contacts/{contact}`

Take one contact out of a list.

`contact` is the contact's `ct-…` id or UUID, or a phone number in E.164 (URL-encode the `+` as `%2B`). The contact itself, its numbers and its opt-outs are untouched.

**Parameters**

<div class="ref-table">

| name | in | type | description |
|---|---|---|---|
| `id` | path | string, required | The list's `cl-…` id or UUID. |
| `contact` | path | string, required | `ct-…`, a UUID, or E.164. |

</div>

**Answers** `200` with the object below. Errors use the [error envelope](/platform-api/reference/#v1error); common codes: [contact lists](/platform-api/errors/#contact-lists), [lists](/platform-api/errors/#lists).

<div class="ref-table">

| field | type | description |
|---|---|---|
| `id` | string, required |   |
| `object` | string, required<br>always `contact_list_member` |   |
| `list` | string, required |   |
| `deleted` | boolean, required<br>always `true` |   |

</div>

Example: see [Run a campaign](/platform-api/campaigns/#finding-ids).

## Objects

### ContactListList

<div class="ref-table">

| field | type | description |
|---|---|---|
| `object` | string, required<br>always `list` |   |
| `data` | array of [ContactList](#contactlist), required |   |
| `has_more` | boolean, required |   |
| `next_cursor` | string or null, required | Pass as `after` for the next page; `null` on the last. |

</div>

Example: see [Finding ids](/platform-api/campaigns/#finding-ids).

### ContactList

<div class="ref-table">

| field | type | description |
|---|---|---|
| `id` | string, required | `cl-…`: pass as `audience.list`. |
| `object` | string, required<br>always `contact_list` |   |
| `uuid` | string, required |   |
| `name` | string, required |   |
| `source` | string, required<br>`api`, `dashboard`, `campaign` | Who made it: `api` (`POST /v1/lists`), `campaign` (imported from a campaign's inline `contacts` or `csv`; removed when that campaign is deleted, unless another campaign uses it), `dashboard` (the dashboard, a CSV upload or Octo). |
| `contacts` | integer, required | How many contacts the list holds (deleted contacts are not counted). |
| `created_at` | string or null, required |   |

</div>

Example: see [Finding ids](/platform-api/campaigns/#finding-ids).

### ContactListCreate

<div class="ref-table">

| field | type | description |
|---|---|---|
| `name` | string, required<br>1–200 chars |   |
| `contacts` | array of objects<br>≤ 10,000 items | Up to 10,000 rows, the same rows as a campaign's `audience.contacts`. Each phone is stored for SMS, calls and WhatsApp alike (one contact per number), so the list works for a campaign on any channel; that is not consent: opt-outs stay on the number. Leave it out for an empty list; an empty array is `400 empty_audience_input`. |
| `contacts[].phone` | string, required<br>pattern `^\+[1-9]\d{6,14}$` | Same as [CampaignAudience.contacts[].phone](/platform-api/reference/campaigns/#campaignaudience). |
| `contacts[].name` | string<br>≤ 200 chars | Same as [CampaignAudience.contacts[].name](/platform-api/reference/campaigns/#campaignaudience). |
| `contacts[].email` | string<br>≤ 320 chars |   |
| `contacts[].company` | string<br>≤ 200 chars |   |
| `contacts[].attributes` | map of string to string or number or boolean | Custom fields, readable as `{{contact.<key>}}` in `variables`. |
| `contact_ids` | array of string<br>≤ 1,000 items; each ≤ 64 chars | Up to 1,000 contacts you already have, by `ct-…` id or UUID, as on `POST /v1/lists/{id}/contacts`. One that is not a contact of this workspace (or was deleted) is skipped and named in `import.errors`. With `contacts`, both are added. A request that names people and would add nobody is `400 empty_audience_input` and creates no list; an empty array is `400 empty_audience_input` too. |

</div>

```json
{
  "name": "October open house",
  "contact_ids": [
    "ct-00000000a1"
  ]
}
```

### ContactListUpdate

Rename a list. Its contacts are changed with `POST /v1/lists/{id}/contacts`.

<div class="ref-table">

| field | type | description |
|---|---|---|
| `name` | string, required<br>1–200 chars | The list's new name (1 to 200 characters, trimmed). |

</div>

```json
{
  "name": "November open house"
}
```

### ContactListWithImport

<div class="ref-table">

| field | type | description |
|---|---|---|
| `id` | string, required | `cl-…`: pass as `audience.list`. |
| `object` | string, required<br>always `contact_list` |   |
| `uuid` | string, required |   |
| `name` | string, required |   |
| `source` | string, required<br>`api`, `dashboard`, `campaign` | Who made it: `api` (`POST /v1/lists`), `campaign` (imported from a campaign's inline `contacts` or `csv`; removed when that campaign is deleted, unless another campaign uses it), `dashboard` (the dashboard, a CSV upload or Octo). |
| `contacts` | integer, required | How many contacts the list holds (deleted contacts are not counted). |
| `created_at` | string or null, required |   |
| `import` | object, required | What this request's import did (`source` is `contacts`). |
| `import.source` | string or null, required<br>`contacts`, `csv` | Which inline audience was imported: `audience.contacts` or `audience.csv`. |
| `import.received` | integer, required | Rows received (blank CSV lines excluded). |
| `import.imported` | integer, required | New contacts made. |
| `import.matched_existing` | integer, required | Rows whose number a contact already had: that contact was used. |
| `import.invalid_rows` | integer, required | Rows dropped because their phone number could not be read. |
| `import.duplicate_in_file` | integer, required | Rows that repeated a number earlier in the same audience. |
| `import.skipped_rows` | integer, required | Blank CSV lines skipped. |
| `import.race_lost` | integer, required | Same as [Campaign.audience.import.race_lost](/platform-api/reference/campaigns/#campaign). |
| `import.errors` | array of objects, required | Each dropped or partly read row: its line and why. |
| `import.errors[].row` | integer, required |   |
| `import.errors[].message` | string, required |   |
| `import.warnings` | array of string, required | Same as [Campaign.audience.import.warnings](/platform-api/reference/campaigns/#campaign). |

</div>

Example: see [Run a campaign](/platform-api/campaigns/#finding-ids).

### ContactListAddContacts

`contacts`, `contact_ids`, or both.

<div class="ref-table">

| field | type | description |
|---|---|---|
| `contacts` | array of objects<br>≤ 10,000 items | 1 to 10,000 rows, as on `POST /v1/lists`. A number a contact already has reuses that contact; one already in the list stays once. |
| `contacts[].phone` | string, required<br>pattern `^\+[1-9]\d{6,14}$` | Same as [CampaignAudience.contacts[].phone](/platform-api/reference/campaigns/#campaignaudience). |
| `contacts[].name` | string<br>≤ 200 chars | Same as [CampaignAudience.contacts[].name](/platform-api/reference/campaigns/#campaignaudience). |
| `contacts[].email` | string<br>≤ 320 chars |   |
| `contacts[].company` | string<br>≤ 200 chars |   |
| `contacts[].attributes` | map of string to string or number or boolean | Custom fields, readable as `{{contact.<key>}}` in `variables`. |
| `contact_ids` | array of string<br>≤ 1,000 items; each ≤ 64 chars | Up to 1,000 contacts you already have, by `ct-…` id or UUID. One that is not a contact of this workspace (or was deleted) is skipped and named in `import.errors`. |

</div>

```json
{
  "contact_ids": [
    "ct-00000000a1"
  ]
}
```

### ContactListMemberList

<div class="ref-table">

| field | type | description |
|---|---|---|
| `object` | string, required<br>always `list` |   |
| `data` | array of [ContactListMember](#contactlistmember), required |   |
| `has_more` | boolean, required |   |
| `next_cursor` | string or null, required | Pass as `after` for the next page; `null` on the last. |

</div>

```json
{
  "object": "list",
  "data": [
    {
      "id": "ct-00000000a1",
      "object": "contact",
      "uuid": "00000000-0000-4000-8000-000000000012",
      "name": null,
      "phone": null,
      "email": null,
      "added_at": null
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

### ContactListMember

<div class="ref-table">

| field | type | description |
|---|---|---|
| `id` | string, required | The contact's `ct-…` id. |
| `object` | string, required<br>always `contact` |   |
| `uuid` | string, required |   |
| `name` | string or null, required |   |
| `phone` | string or null, required | E.164, or null for none. |
| `email` | string or null, required |   |
| `added_at` | string or null, required | When it joined the list (ISO 8601). |

</div>

```json
{
  "id": "ct-00000000a1",
  "object": "contact",
  "uuid": "00000000-0000-4000-8000-000000000012",
  "name": null,
  "phone": null,
  "email": null,
  "added_at": null
}
```