---
title: "Audiences"
description: "Every operation in this group: what it accepts, what it returns and the errors it can answer with."
url: "https://openemail.uk/docs/api/reference/audiences"
area: "API"
category: "Reference"
---

# Audiences

Every operation in this group: what it accepts, what it returns and the errors it can answer with.

## Operations

A named list of contacts in this workspace. Every contact is in the built-in default audience from the moment it exists, and `builtin` is what names that row; the rest are yours to make, fill and delete.

Send to one or more audiences with `POST /broadcasts`. A contact who unsubscribes from a broadcast stays in the audience with `unsubscribedAt` set, and later broadcasts to it skip them. Deleting an audience drops its memberships and leaves every contact in the book.

### `GET /audiences`

List audiences

The built-in default audience comes first, then the rest newest first, one page at a time. `contactCount` on each row is counted at the moment of the read, so two reads either side of a create disagree by one.

The default audience is resolved on the first read, so a workspace that has never made an audience still lists exactly one row.

Requires the `audiences:read` scope.

- Scopes: `audiences:read`.

**Query parameters**

- `limit` (`integer`, at least 1, at most 100, default `25`): Rows per page, 1 to 100.
- `cursor` (`string`): An audience id. Keyset, not offset: pass the previous page's `nextCursor`. One that names nothing in this list is a 400 `invalid_cursor`.

**Returns**

- `200` `AudienceList`: A page of the audiences in this workspace.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`audiences.list()`](https://openemail.uk/docs/sdk/reference/audiences#list), [`audiences.listAll()`](https://openemail.uk/docs/sdk/reference/audiences#listAll), [`audiences.iterate()`](https://openemail.uk/docs/sdk/reference/audiences#iterate); CLI [`openemail audiences list`](https://openemail.uk/docs/cli/reference/audiences#audiences-list); MCP [`listAudiences`](https://openemail.uk/docs/mcp/tools/audiences#listAudiences).

### `POST /audiences`

Create an audience

Makes an empty audience. `name` is trimmed and names are not checked for duplicates, because an audience is addressed by its id.

`builtin` is never accepted from the body: exactly one row per workspace carries it and the server owns that row.

Requires the `audiences:write` scope.

- Scopes: `audiences:write`.

**Request body**

- `name` (`string`, required, 1 to 120 characters): Display name, trimmed, 1 to 120 characters. Not unique.
- `description` (`string`, nullable, up to 1000 characters): What the audience is for. Leave it out to create the audience without one.

**Returns**

- `201` `Audience`: Created.

**Errors**

- `422`: `workspace_limit_reached` when the workspace already holds the most audiences it may have.
- The errors every operation can return: `400`, `401`, `403`, `404`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`audiences.create()`](https://openemail.uk/docs/sdk/reference/audiences#create); CLI [`openemail audiences create`](https://openemail.uk/docs/cli/reference/audiences#audiences-create); MCP [`createAudience`](https://openemail.uk/docs/mcp/tools/audiences#createAudience).

### `GET /audiences/growth`

How audiences grew over a window

The numbers behind the growth chart on the Audiences page, in one request: for each audience, how many contacts it holds now, how many of those joined before the window, and when the rest joined, bucketed by `grain`.

An audience records the date each contact joined it and never the date one left. A series therefore counts the contacts still in the list today by the date they joined, and it never falls: a contact who joined inside the window and was later removed does not appear in it at all.

`totals.contacts` counts each person once. `totals.memberships` adds the lists up, and the default audience holds every contact, so a person counts once for every list they are in.

Requires the `audiences:read` scope.

- Scopes: `audiences:read`.

**Query parameters**

- `audienceIds` (`string`, up to 4000 characters): Comma separated audience ids, at most 50. Leave it out to read every audience in the workspace, the default one included. A repeated id counts once.
- `days` (`integer`, at least 1, at most 1095, default `30`): How far back to look. The window starts at the beginning of its first bucket in the offset you asked for, so the oldest bucket is a whole one, and ends now, so the newest is partial.
- `minutes` (`integer`, at least 1, at most 1576800): The window in minutes, which wins over `days` when both are sent. Use it with an `hour` or `minute` grain to watch an import land.
- `grain` (`string`, one of `"minute"`, `"hour"`, `"day"`, default `"day"`): How wide one bucket is, and the shape of its key: `YYYY-MM-DD` for a day, `YYYY-MM-DDTHH` for an hour, `YYYY-MM-DDTHH:MM` for a minute.
- `offsetMinutes` (`integer`, at least -840, at most 840, default `0`): Minutes east of UTC to cut the buckets in, so a day breaks where the reader's day does rather than at midnight UTC.

**Returns**

- `200` `AudienceGrowth`: The window and one series per audience. Buckets are sparse.

**Errors**

- `404`: `audience_not_found` on `audienceIds` when an id names no audience in this workspace. Nothing is read.
- `422`: `invalid_parameter` on `audienceIds` for more than 50 ids, on `days`, `minutes` or `offsetMinutes` out of range, or on an unknown `grain`.
- The errors every operation can return: `400`, `401`, `403`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`audiences.growth()`](https://openemail.uk/docs/sdk/reference/audiences#growth); CLI [`openemail audiences growth`](https://openemail.uk/docs/cli/reference/audiences#audiences-growth); MCP [`getAudienceGrowth`](https://openemail.uk/docs/mcp/tools/audiences#getAudienceGrowth).

### `GET /audiences/{id}`

Retrieve an audience

The same shape as the list row, with a fresh `contactCount`. This is the cheap way to watch a count move without pulling the contacts behind it. An audience in another workspace is a 404, never a 403.

Requires the `audiences:read` scope.

- Scopes: `audiences:read`.

**Path parameters**

- `id` (`string`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`.

**Returns**

- `200` `Audience`: The audience.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`audiences.get()`](https://openemail.uk/docs/sdk/reference/audiences#get); CLI [`openemail audiences get`](https://openemail.uk/docs/cli/reference/audiences#audiences-get); MCP [`getAudience`](https://openemail.uk/docs/mcp/tools/audiences#getAudience).

### `PATCH /audiences/{id}`

Update an audience

A partial update. The built-in default audience can be renamed and described like any other, and renaming it changes neither `builtin` nor what it holds. Membership is untouched here.

Requires the `audiences:write` scope.

- Scopes: `audiences:write`.

**Path parameters**

- `id` (`string`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`.

**Request body**

- `name` (`string`, 1 to 120 characters): New display name, trimmed, 1 to 120 characters.
- `description` (`string`, nullable, up to 1000 characters): New description. Null clears it.

**Returns**

- `200` `Audience`: Saved.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`audiences.update()`](https://openemail.uk/docs/sdk/reference/audiences#update); CLI [`openemail audiences update`](https://openemail.uk/docs/cli/reference/audiences#audiences-update); MCP [`updateAudience`](https://openemail.uk/docs/mcp/tools/audiences#updateAudience).

### `DELETE /audiences/{id}`

Delete an audience

Deletes the audience and its memberships. The contacts themselves stay in the book, in the default audience, and in any other audience they were in.

Requires the `audiences:write` scope.

- Scopes: `audiences:write`.
- Asks an OAuth access token for a verification code.

**Path parameters**

- `id` (`string`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`. Not the default audience.

**Returns**

- `200`: Deleted.

**Errors**

- `403`: The key lacks the scope, or may not send as that address. `step_up_required`: the call was made with an OAuth access token that has not been verified in the last 60 minutes. Ask for a code with `POST /security/step-up`, send it to `POST /security/step-up/verify`, then repeat the call. The person can also choose Allow changes for 60 minutes on the app in Account settings, Connected apps, on the OpenEmail website. An API key is never asked for a code.
- `409`: `audience_immutable`: the built-in default audience cannot be deleted.
- The errors every operation can return: `400`, `401`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`audiences.delete()`](https://openemail.uk/docs/sdk/reference/audiences#delete); CLI [`openemail audiences delete`](https://openemail.uk/docs/cli/reference/audiences#audiences-delete); MCP [`deleteAudience`](https://openemail.uk/docs/mcp/tools/audiences#deleteAudience).

### `GET /audiences/{id}/contacts`

List an audience's contacts

The contacts themselves rather than membership records, one page at a time, each with `addedAt`, the date it joined this audience. Every contact in the audience is reachable by following `nextCursor`, which makes this the way to export an audience. Reading the built-in default audience here returns the whole address book.

`audiences:read` is the only scope checked, so a key with it reads the addresses in an audience without holding `contacts:read`.

Requires the `audiences:read` scope.

- Scopes: `audiences:read`.

**Path parameters**

- `id` (`string`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`.

**Query parameters**

- `limit` (`integer`, at least 1, at most 200, default `50`): Rows per page, 1 to 200.
- `cursor` (`string`): The previous page's `nextCursor`, never built by hand. Send the same `q`, `source` and `sort` with it. Keyset, not offset: pass the previous page's `nextCursor`. One that names nothing in this list is a 400 `invalid_cursor`.
- `q` (`string`, up to 200 characters): Narrows the page to contacts whose name or address matches. When nothing in the audience matches exactly, the search allows for a typo instead, and later pages of the same query keep to the same kind of match.
- `source` (`string`, one of `"manual"`, `"auto"`, `"form"`): Narrows the page to contacts recorded that way.
- `sort` (`string`, one of `"last-heard-newest"`, `"last-heard-oldest"`, `"added-newest"`, `"added-oldest"`, `"name"`, default `"last-heard-newest"`): `last-heard-newest` puts the contacts most recently written to first and those never written to last, the order of `GET /contacts`. `last-heard-oldest` reverses it. `added-newest` and `added-oldest` order by `addedAt`, the date each contact joined this audience. `name` is alphabetical without case, and a contact with no name sorts by its address.
- `status` (`string[]`, one of `"subscribed"`, `"unsubscribed"`): Comma-separated. `subscribed` keeps the members who have not unsubscribed, `unsubscribed` keeps the ones who have. Leave it out, or name both, for everyone in the audience.

**Returns**

- `200` `AudienceContactList`: A page of the contacts in this audience.

**Errors**

- `400`: `invalid_cursor` when `cursor` names no contact in this audience.
- `404`: `audience_not_found` when the id names no audience in this workspace.
- `422`: `invalid_parameter` on an unknown `sort`, `source` or `status`, a `limit` out of range or a `q` over 200 characters.
- The errors every operation can return: `401`, `403`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`audiences.listContacts()`](https://openemail.uk/docs/sdk/reference/audiences#listContacts), [`audiences.listAllContacts()`](https://openemail.uk/docs/sdk/reference/audiences#listAllContacts), [`audiences.iterateContacts()`](https://openemail.uk/docs/sdk/reference/audiences#iterateContacts); CLI [`openemail audiences list-contacts`](https://openemail.uk/docs/cli/reference/audiences#audiences-list-contacts); MCP [`listAudienceContacts`](https://openemail.uk/docs/mcp/tools/audiences#listAudienceContacts).

### `POST /audiences/{id}/contacts`

Add a contact to an audience

Takes an address already in the workspace book. Adding one that is already in the audience changes nothing, answers 200 and carries the original `addedAt`, so a retry is safe.

An address that is not a contact is refused rather than created on the spot. Save it with `POST /contacts` first. To add many contacts in one call, use `POST /audiences/{id}/contacts/batch`.

Requires the `audiences:write` scope.

- Scopes: `audiences:write`.

**Path parameters**

- `id` (`string`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`.

**Request body**

- `email` (`string`, required, up to 320 characters, format `email`): An address already in the workspace book. An unknown address is refused rather than created on the spot.

**Returns**

- `200` `AudienceMember`: The membership, with the contact it points at.

**Errors**

- `422`: `contact_not_found` on `email` when that address is not in the workspace book.
- The errors every operation can return: `400`, `401`, `403`, `404`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`audiences.addContact()`](https://openemail.uk/docs/sdk/reference/audiences#addContact); CLI [`openemail audiences add-contact`](https://openemail.uk/docs/cli/reference/audiences#audiences-add-contact); MCP [`addContactToAudience`](https://openemail.uk/docs/mcp/tools/audiences#addContactToAudience).

### `DELETE /audiences/{id}/contacts/{email}`

Remove a contact from an audience

Removes the membership. The contact stays in the book, stays in the default audience and stays in every other audience it was in. A contact that is not in this audience is a 404, so a typo cannot report a removal that never happened.

Requires the `audiences:write` scope.

- Scopes: `audiences:write`.

**Path parameters**

- `id` (`string`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`. Not the default audience.
- `email` (`string`, required): The contact's address, matched case insensitively.

**Returns**

- `200`: Removed.

**Errors**

- `404`: `audience_not_found` for the `id`, `contact_not_found` on `email` when that address is not in the workspace book, or `audience_member_not_found` when the contact exists but is not in this audience.
- `409`: `audience_immutable`: the built-in default audience holds every contact for as long as it is a contact. Delete the contact to take it out.
- The errors every operation can return: `400`, `401`, `403`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`audiences.removeContact()`](https://openemail.uk/docs/sdk/reference/audiences#removeContact); CLI [`openemail audiences remove-contact`](https://openemail.uk/docs/cli/reference/audiences#audiences-remove-contact); MCP [`removeContactFromAudience`](https://openemail.uk/docs/mcp/tools/audiences#removeContactFromAudience).

### `POST /audiences/{id}/contacts/batch`

Add many contacts to an audience

Puts up to 200 existing contacts in the audience in one call, in one transaction. It never creates a contact: an address that is not in the workspace book comes back in `missing` and the rest are still added. To create contacts as you add them, use `POST /audiences/{id}/import`.

A contact already in the audience is counted in `unchanged` and keeps its original `addedAt`, so the call is safe to replay. Adding to the built-in default audience answers `added: 0`, because every contact is in it already.

Requires the `audiences:write` scope.

- Scopes: `audiences:write`.

**Path parameters**

- `id` (`string`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`.

**Request body**

- `emails` (`string[]`, required, 1 to 200 items): 1 to 200 addresses. Each is trimmed and lower cased and a repeat counts once. They are looked up in the workspace book as they stand, so an address that is not a contact comes back in `missing` rather than refusing the call.

**Returns**

- `200` `AudienceBatchAdd`: What the call added, what was there already and what is not a contact.

**Errors**

- `404`: `audience_not_found` when the id names no audience in this workspace.
- `422`: `invalid_parameter` on `emails` when it is empty or holds more than 200 addresses, and on the entry, such as `emails.2`, when an address is empty or too long, `unknown_parameter` for any other key in the body.
- The errors every operation can return: `400`, `401`, `403`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`audiences.addContacts()`](https://openemail.uk/docs/sdk/reference/audiences#addContacts); CLI [`openemail audiences add-contacts`](https://openemail.uk/docs/cli/reference/audiences#audiences-add-contacts); MCP [`addContactsToAudience`](https://openemail.uk/docs/mcp/tools/audiences#addContactsToAudience).

### `POST /audiences/{id}/contacts/batch-remove`

Remove many contacts from an audience

Takes up to 200 contacts out of the audience in one call, in one transaction. The contacts stay in the book, in the default audience and in every other audience they are in.

Nothing is refused for one address: a contact that is not in this audience comes back in `notInAudience` and an address that is not a contact in `missing`, and the rest are still removed. A replay therefore succeeds and reports the contacts the first call removed under `notInAudience`.

Requires the `audiences:write` scope.

- Scopes: `audiences:write`.

**Path parameters**

- `id` (`string`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`. Not the default audience.

**Request body**

- `emails` (`string[]`, required, 1 to 200 items): 1 to 200 addresses. Each is trimmed and lower cased and a repeat counts once. They are looked up in the workspace book as they stand, so an address that is not a contact comes back in `missing` rather than refusing the call.

**Returns**

- `200` `AudienceBatchRemove`: What the call removed, what was not in the audience and what is not a contact.

**Errors**

- `404`: `audience_not_found` when the id names no audience in this workspace.
- `409`: `audience_immutable`: the built-in default audience holds every contact for as long as it is a contact. Delete the contacts to take them out.
- `422`: `invalid_parameter` on `emails` when it is empty or holds more than 200 addresses, and on the entry, such as `emails.2`, when an address is empty or too long, `unknown_parameter` for any other key in the body.
- The errors every operation can return: `400`, `401`, `403`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`audiences.removeContacts()`](https://openemail.uk/docs/sdk/reference/audiences#removeContacts); CLI [`openemail audiences remove-contacts`](https://openemail.uk/docs/cli/reference/audiences#audiences-remove-contacts); MCP [`removeContactsFromAudience`](https://openemail.uk/docs/mcp/tools/audiences#removeContactsFromAudience).

### `POST /audiences/{id}/import`

Import contacts into an audience

What the CSV import on an audience page does, without the file: up to 500 rows of an address and an optional name, in one transaction. An address that is not a contact yet is saved as one, with `source` set to `manual`, and joins the default audience too. An address that is a contact already is reused as it stands and keeps its name: a name sent here only fills one that is empty. Every imported contact ends up in this audience.

A row whose address is not well formed is skipped and returned in `invalid`, and the other rows are still imported. Importing an address whose contact was deleted brings it back. Replaying the same rows creates nothing twice, so the call is safe to retry. Send a longer list in several calls.

Requires the `audiences:write` and `contacts:write` scopes.

- Scopes: `audiences:write`, `contacts:write`.

**Path parameters**

- `id` (`string`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`. The default audience is accepted and saves the contacts without putting them in any other list.

**Request body**

- `contacts` (`object[]`, required, 1 to 500 items): 1 to 500 rows. Send a longer list in several calls. Rows with the same address, compared without case, count as one.
  - `email` (`string`, required, 1 to 320 characters): Trimmed and lower cased. A row whose address is not well formed is skipped and listed in `invalid`, and the other rows are still imported.
  - `name` (`string`, nullable, up to 200 characters): Trimmed. Used for a new contact, or for an existing one whose name is empty. A name already saved is kept.

**Returns**

- `200` `AudienceImport`: What the import created, added and skipped.

**Errors**

- `403`: `insufficient_scope` unless the key holds both `audiences:write` and `contacts:write`.
- `404`: `audience_not_found` when the id names no audience in this workspace.
- `422`: `invalid_parameter` on `contacts` when it is empty or holds more than 500 rows, or on a row whose `email` is empty or longer than 320 characters or whose `name` is longer than 200, `unknown_parameter` for any other key.
- The errors every operation can return: `400`, `401`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`audiences.importContacts()`](https://openemail.uk/docs/sdk/reference/audiences#importContacts); CLI [`openemail audiences import-contacts`](https://openemail.uk/docs/cli/reference/audiences#audiences-import-contacts); MCP [`importContactsToAudience`](https://openemail.uk/docs/mcp/tools/audiences#importContactsToAudience).

### `POST /audiences/{id}/empty`

Empty an audience

Takes every contact out of the audience in one transaction and answers with the audience as it now stands, `contactCount` 0, plus `removed`, the number of contacts taken out. The audience itself stays, with its name and id, and so does every contact: each one stays in the book, in the default audience and in its other audiences. No body.

There is no undo, so check the audience id before you call this.

Requires the `audiences:write` scope.

- Scopes: `audiences:write`.

**Path parameters**

- `id` (`string`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`. Not the default audience.

**Returns**

- `200` `object`: The audience, now empty.
  - `object` (`string`, one of `"audience"`)
  - `id` (`string`): The durable handle, `aud_` plus 24 hex.
  - `name` (`string`)
  - `description` (`string`, nullable)
  - `builtin` (`string`, nullable, one of `"default"`): Which built-in audience this row is: `default` for the one every contact joins, null for one somebody created. Branch on this rather than on the name, which anybody can change.
  - `contactCount` (`integer`): Counted at the moment of the read, never cached.
  - `lastContactAt` (`string`, nullable, format `date-time`): When the contact who joined most recently joined this audience, read at the moment of the request. Null while the audience is empty.
  - `createdAt` (`string`, format `date-time`)
  - `updatedAt` (`string`, format `date-time`)
  - `removed` (`integer`): Contacts taken out of the audience. 0 when it was empty already.

**Errors**

- `404`: `audience_not_found` when the id names no audience in this workspace.
- `409`: `audience_immutable`: the built-in default audience holds every contact for as long as it is a contact, so it cannot be emptied.
- The errors every operation can return: `400`, `401`, `403`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`audiences.empty()`](https://openemail.uk/docs/sdk/reference/audiences#empty); CLI [`openemail audiences empty`](https://openemail.uk/docs/cli/reference/audiences#audiences-empty); MCP [`emptyAudience`](https://openemail.uk/docs/mcp/tools/audiences#emptyAudience).

### Objects

#### `Audience`

`object`

- `object` (`string`, one of `"audience"`)
- `id` (`string`): The durable handle, `aud_` plus 24 hex.
- `name` (`string`)
- `description` (`string`, nullable)
- `builtin` (`string`, nullable, one of `"default"`): Which built-in audience this row is: `default` for the one every contact joins, null for one somebody created. Branch on this rather than on the name, which anybody can change.
- `contactCount` (`integer`): Counted at the moment of the read, never cached.
- `lastContactAt` (`string`, nullable, format `date-time`): When the contact who joined most recently joined this audience, read at the moment of the request. Null while the audience is empty.
- `createdAt` (`string`, format `date-time`)
- `updatedAt` (`string`, format `date-time`)

#### `AudienceBatchAdd`

`object`

- `object` (`string`, one of `"audience_batch"`)
- `audienceId` (`string`)
- `added` (`integer`): Contacts that joined the audience in this call.
- `unchanged` (`integer`): Contacts that were in the audience already. Their `addedAt` is kept.
- `missing` (`string[]`): The addresses that are not contacts in this workspace, trimmed, lower cased and listed once each. Nothing is created for them. Save them with `POST /contacts`, or use `POST /audiences/{id}/import`, which creates contacts as it goes.

#### `AudienceBatchRemove`

`object`

- `object` (`string`, one of `"audience_batch"`)
- `audienceId` (`string`)
- `removed` (`integer`): Contacts taken out of the audience in this call.
- `notInAudience` (`string[]`): Addresses of contacts that exist but were not in this audience, so there was nothing to remove for them.
- `missing` (`string[]`): The addresses that are not contacts in this workspace, trimmed, lower cased and listed once each. Nothing is created for them. Save them with `POST /contacts`, or use `POST /audiences/{id}/import`, which creates contacts as it goes.

#### `AudienceContact`

`object`

- `object` (`string`, one of `"contact"`)
- `email` (`string`): Trimmed and lower cased on write. This is the key every contact route takes, because no contact id is exposed.
- `name` (`string`, nullable)
- `source` (`string`, one of `"manual"`, `"auto"`, `"form"`): `auto` when a send from the app composer recorded the address, `manual` when somebody saved it here or in the app. A recorded send never downgrades a `manual` contact to `auto`.
- `notes` (`string`, nullable)
- `photoUrl` (`string`, nullable): Where the contact photo is served, or null when the contact has none. Set it with `PUT /contacts/{email}/photo`. A new upload gets a new URL.
- `lastSeenAt` (`string`, nullable, format `date-time`): When mail last went to this address from the app composer. Null until it does.
- `createdAt` (`string`, format `date-time`)
- `updatedAt` (`string`, format `date-time`)
- `addedAt` (`string`, format `date-time`): When the contact joined this audience. The `added-newest` and `added-oldest` sorts order by it, and a repeated add keeps the original date.
- `unsubscribedAt` (`string`, nullable, format `date-time`): When the contact followed the unsubscribe link in a broadcast sent to this audience, or null while it is subscribed. An unsubscribed contact stays in the audience and in the book, and broadcasts to this audience skip it. Removing it from the audience and adding it back makes a fresh, subscribed membership. Mail sent to it one message at a time is not affected.

#### `AudienceContactList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`AudienceContact[]`)
- `hasMore` (`boolean`)
- `nextCursor` (`string`, nullable)
- `audienceId` (`string`)

#### `AudienceGrowth`

`object`

- `object` (`string`, one of `"audience_growth"`)
- `since` (`string`, format `date-time`): The start of the window, floored to the start of its first bucket in the local time of `offsetMinutes`.
- `until` (`string`, format `date-time`): The end of the window, which is the moment of the read.
- `grain` (`string`, one of `"minute"`, `"hour"`, `"day"`)
- `offsetMinutes` (`integer`): The offset the buckets were cut in, as sent or 0.
- `totals` (`object`)
  - `contacts` (`integer`): Distinct contacts across the audiences read, each counted once however many of them it is in.
  - `subscribed` (`integer`): Of those, the contacts still subscribed to at least one of the audiences read, so a broadcast to them would reach them.
  - `memberships` (`integer`): The series `total` values added up. The default audience holds every contact, so a contact in two other audiences counts three times here.
  - `added` (`integer`): Joins inside the window across every series.
  - `unsubscribed` (`integer`): Unsubscribes inside the window across every series.
  - `lists` (`integer`): How many audiences were read.
  - `busiest` (`string`, nullable): The bucket key with the most joins across every series, the earliest one on a tie, or null when nobody joined in the window.
- `series` (`object[]`): One per audience read, the largest first and then by name.
  - `id` (`string`): The audience id.
  - `name` (`string`)
  - `builtin` (`boolean`): True for the default audience, the one that holds every contact. A boolean here, where the audience object carries the `builtin` kind.
  - `total` (`integer`): Contacts in the audience now.
  - `subscribed` (`integer`): Of those, the contacts still subscribed. The rest unsubscribed and are skipped by broadcasts.
  - `before` (`integer`): Of those, the contacts that joined before `since`.
  - `added` (`integer`): Of those, the contacts that joined inside the window. With `before` it makes up `total`.
  - `unsubscribed` (`integer`): Contacts that unsubscribed from the audience inside the window.
  - `buckets` (`object[]`): SPARSE, oldest first. Only buckets in which somebody joined or unsubscribed are listed, so a chart has to fill the gaps with zero itself.
    - `bucket` (`string`): The bucket's key in the local time of `offsetMinutes`: `YYYY-MM-DD` for a day, `YYYY-MM-DDTHH` for an hour, `YYYY-MM-DDTHH:MM` for a minute.
    - `added` (`integer`): Contacts that joined the audience in that bucket and are still in it.
    - `unsubscribed` (`integer`): Contacts that unsubscribed from the audience in that bucket, through a broadcast unsubscribe link.

#### `AudienceImport`

`object`

- `object` (`string`, one of `"audience_import"`)
- `audienceId` (`string`)
- `created` (`integer`): Addresses that were not contacts yet and were saved as new ones, with `source` set to `manual`.
- `added` (`integer`): Contacts that joined this audience in this call, new and existing together. A contact already in it is not counted.
- `skipped` (`integer`): Rows that were not imported because the address is not well formed.
- `invalid` (`string[]`): The addresses of the skipped rows, exactly as they were sent.

#### `AudienceList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`Audience[]`)
- `hasMore` (`boolean`)
- `nextCursor` (`string`, nullable)

#### `AudienceMember`

`object`

- `object` (`string`, one of `"audience_member"`)
- `audienceId` (`string`)
- `addedAt` (`string`, format `date-time`): When the contact first joined this audience. A repeat add answers with the original date rather than a new one.
- `contact` (`Contact`)

#### `Contact`

`object`

- `object` (`string`, one of `"contact"`)
- `email` (`string`): Trimmed and lower cased on write. This is the key every contact route takes, because no contact id is exposed.
- `name` (`string`, nullable)
- `source` (`string`, one of `"manual"`, `"auto"`, `"form"`): `auto` when a send from the app composer recorded the address, `manual` when somebody saved it here or in the app. A recorded send never downgrades a `manual` contact to `auto`.
- `notes` (`string`, nullable)
- `photoUrl` (`string`, nullable): Where the contact photo is served, or null when the contact has none. Set it with `PUT /contacts/{email}/photo`. A new upload gets a new URL.
- `lastSeenAt` (`string`, nullable, format `date-time`): When mail last went to this address from the app composer. Null until it does.
- `createdAt` (`string`, format `date-time`)
- `updatedAt` (`string`, format `date-time`)
