---
title: "client.audiences"
description: "Every method in this namespace: its signature, its parameters, what it returns and an example."
url: "https://openemail.uk/docs/ruby/reference/audiences"
area: "Ruby"
category: "Reference"
---

# client.audiences

Every method in this namespace: its signature, its parameters, what it returns and an example.

## Methods

Named lists of contacts. Every contact is in the default audience, and you make the rest.

### `audiences.list`

List one page of the workspace audiences

```ruby
list(limit: nil, cursor: nil, api_key: nil) -> OpenEmail::Page
```

Returns one page of the audiences in the workspace, with the default audience first and the rest newest first. Paging is keyset: `limit:` takes 1 to 100 and defaults to 25, and `next_cursor` goes back as `cursor:` while `has_more?` is true, so every audience is reachable. `list_all` and `iterate` do that walk for you.

`builtin` tells the default audience apart from the ones you made. It is `default` on exactly one row per workspace, the audience that holds every contact, and nil on everything else. Branch on `builtin` rather than on the name, which anybody can change.

`contactCount` is counted at the moment of the read, so two reads either side of a `contacts.create` disagree by one.

Scopes: `audiences:read`.

**Parameters**

- `limit` (`Integer`): Rows per page, a whole number from 1 to 100. The server defaults to 25.
- `cursor` (`String`): The `next_cursor` from the previous page, an audience id. One that names no audience in the workspace is a 400 `invalid_cursor`.
- `api_key` (`String`): Overrides the client's API key for this call only.

**Returns**

An `OpenEmail::Page` with `items`, `has_more?` and `next_cursor`. Each item is a Hash with `id`, `name`, `description`, `builtin`, `contactCount`, `lastContactAt`, `createdAt` and `updatedAt`.

**Example**

```ruby
page = client.audiences.list
everyone = page.items.find { |audience| audience[:builtin] == "default" } || {}

puts "#{everyone.fetch(:contactCount, 0)} contacts in #{everyone[:name]}"
```

**Notes**

- The default audience is resolved on the first read of the workspace, so a workspace that has never had an audience made still lists it.
- Names are not unique. Two audiences can both be called Newsletter, so match on `id` in stored configuration.

Also available in: API [`GET /audiences`](https://openemail.uk/docs/api/reference/audiences#get-audiences); TypeScript [`audiences.list()`](https://openemail.uk/docs/sdk/reference/audiences#list); Python [`audiences.list()`](https://openemail.uk/docs/python/reference/audiences#list); CLI [`openemail audiences list`](https://openemail.uk/docs/cli/reference/audiences#audiences-list).

### `audiences.list_all`

Collect every audience in the workspace into one array

```ruby
list_all(limit: nil, cursor: nil, api_key: nil) -> Array<Hash>
```

Follows `next_cursor` from page to page and returns every audience in the workspace, the default audience first and the rest newest first. `limit:` sets the page size of each request, not the total.

Scopes: `audiences:read`.

**Parameters**

- `limit` (`Integer`): Page size per request, from 1 to 100, defaulting to 25 on the server.
- `cursor` (`String`): An audience id to start after.
- `api_key` (`String`): Overrides the client's API key for every page of this walk.

**Returns**

An Array of Hashes holding every audience across all pages.

**Example**

```ruby
audiences = client.audiences.list_all(limit: 100)

newsletter = audiences.find { |audience| audience[:name] == "Newsletter" }

puts audiences.size
puts newsletter[:contactCount] if newsletter
```

**Notes**

- A failure on any page raises out of the whole call.

Also available in: API [`GET /audiences`](https://openemail.uk/docs/api/reference/audiences#get-audiences); TypeScript [`audiences.listAll()`](https://openemail.uk/docs/sdk/reference/audiences#listAll); Python [`audiences.list_all()`](https://openemail.uk/docs/python/reference/audiences#listAll).

### `audiences.iterate`

Stream the workspace audiences one at a time

```ruby
iterate(limit: nil, cursor: nil, api_key: nil, &block) -> Enumerator<Hash>
```

Returns an Enumerator that yields audiences individually, the default audience first, or yields each one to a block when given one, and requests the next page only once the current one is drained. Breaking out of the loop stops the requests.

Scopes: `audiences:read`.

**Parameters**

- `limit` (`Integer`): Page size per request, from 1 to 100, defaulting to 25 on the server.
- `cursor` (`String`): An audience id to start after.
- `api_key` (`String`): Overrides the client's API key for every page of this walk.

**Returns**

An Enumerator of Hashes, one audience per step, or yields each one to a block.

**Example**

```ruby
client.audiences.iterate do |audience|
  puts "empty: #{audience[:name]}" if audience[:contactCount] == 0
end
```

**Notes**

- The Enumerator is lazy, so an abandoned loop costs only the pages you consumed.

Also available in: API [`GET /audiences`](https://openemail.uk/docs/api/reference/audiences#get-audiences); TypeScript [`audiences.iterate()`](https://openemail.uk/docs/sdk/reference/audiences#iterate); Python [`audiences.iterate()`](https://openemail.uk/docs/python/reference/audiences#iterate).

### `audiences.growth`

Read how audiences grew over a time window

```ruby
growth(audience_ids: nil, days: nil, minutes: nil, grain: nil, offset_minutes: nil, api_key: nil) -> Hash
```

Returns the numbers behind the growth chart on the Audiences page in one request. For each audience it reports `total`, the contacts in it now, `before`, how many of those joined before the window, `added`, how many joined inside it, `subscribed`, how many of them are still subscribed, `unsubscribed`, how many unsubscribed inside the window through a broadcast link, and `buckets`, when they joined and unsubscribed, cut to `grain`. `totals` sums the audiences read.

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 removed since is not in it at all. Read it as the growth of the list as it stands, not as a history of every change.

`totals.contacts` counts each person once, however many of the audiences they are in. `totals.memberships` adds the lists up, and the default audience holds every contact, so a person in two other audiences counts three times there. `totals.subscribed` counts each person still subscribed to at least one of the audiences read, which is who a broadcast to them would reach, and `totals.unsubscribed` adds up the unsubscribes inside the window. `totals.busiest` is the bucket with the most joins, or nil.

`buckets` is sparse and oldest first: a bucket in which nobody joined or unsubscribed has no entry, so a chart must fill the gaps. `grain:` sets the bucket width and the key shape, `YYYY-MM-DD`, `YYYY-MM-DDTHH` or `YYYY-MM-DDTHH:MM`, and `offset_minutes:` shifts the boundaries so days break where the reader's day does. The window starts at the beginning of its oldest bucket, reported as `since`, and ends now, reported as `until`.

Scopes: `audiences:read`.

**Parameters**

- `audience_ids` (`Array<String>`): Up to 50 audience ids to read, sent comma separated. Leave it out, or pass an empty Array, to read every audience in the workspace, the default one included. An id that is not an audience of this workspace is a 404 `audience_not_found` on `audienceIds`, and more than 50 is a 422.
- `days` (`Integer`): Window length in days, from 1 to 1095, defaulting to 30.
- `minutes` (`Integer`): Window length in minutes, from 1 to 1576800. Takes precedence over `days:`, and only useful below a day with a finer `grain:`.
- `grain` (`String`): Bucket width: `minute`, `hour` or `day`, defaulting to `day`.
- `offset_minutes` (`Integer`): Minutes east of UTC to cut the buckets in, from -840 to 840, defaulting to 0. Pass `Time.now.utc_offset / 60` for the local zone.
- `api_key` (`String`): Reads with this key instead of the client's.

**Returns**

A Hash with `object` set to `audience_growth`, `since`, `until`, `grain`, `offsetMinutes`, `totals` and `series`. `totals` has `contacts`, `subscribed`, `memberships`, `added`, `unsubscribed`, `lists` and `busiest`. Each entry of `series` has `id`, `name`, `builtin` as a boolean, `total`, `subscribed`, `before`, `added`, `unsubscribed` and `buckets`, each a Hash with `bucket`, `added` and `unsubscribed`. The series come largest first, then by name.

**Example**

```ruby
growth = client.audiences.growth(days: 90, grain: "day", offset_minutes: Time.now.utc_offset / 60)
totals = growth[:totals]

puts "#{totals[:added]} joins since #{growth[:since]}, busiest #{totals[:busiest]}"

growth[:series].each do |series|
  puts "#{series[:name]} #{series[:before]} -> #{series[:total]}"
end
```

**Notes**

- Read only, so the SDK retries it after a network failure like any other read.
- `builtin` on a series is a boolean, true on the default audience. On the audience Hashes `list` and `get` return, the same fact is the string `default`.
- A running total per bucket is `before` plus the `added` of every bucket up to it.

Also available in: API [`GET /audiences/growth`](https://openemail.uk/docs/api/reference/audiences#get-audiences-growth); TypeScript [`audiences.growth()`](https://openemail.uk/docs/sdk/reference/audiences#growth); Python [`audiences.growth()`](https://openemail.uk/docs/python/reference/audiences#growth); CLI [`openemail audiences growth`](https://openemail.uk/docs/cli/reference/audiences#audiences-growth).

### `audiences.get`

Read one audience by id

```ruby
get(id, api_key: nil) -> Hash
```

Returns a single audience in the same shape as `list`, with a fresh `contactCount`. This is the cheap way to watch a count move without pulling the contacts behind it.

The id is the one from `list` or `create`, such as `aud_9f2c4b7e1a0d63d84c5f2e7b`. An id from another workspace is a 404 rather than a 403, since the API never tells you that something exists somewhere you cannot see.

Scopes: `audiences:read`.

**Parameters**

- `id` (`String`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`.
- `api_key` (`String`): Overrides the client's API key for this call only.

**Returns**

A Hash with `id`, `name`, `description`, `builtin`, `contactCount`, `lastContactAt`, the moment the most recent contact joined or nil while it is empty, `createdAt` and `updatedAt`.

**Example**

```ruby
audience = client.audiences.get("aud_9f2c4b7e1a0d63d84c5f2e7b")

puts audience[:name], audience[:contactCount]
```

**Notes**

- A missing audience raises `OpenEmail::NotFoundError`, an `OpenEmail::ApiError` with `not_found?` true and code `audience_not_found`.
- The default audience can be read here like any other, and `builtin` is `default` on it.

Also available in: API [`GET /audiences/{id}`](https://openemail.uk/docs/api/reference/audiences#get-audiences-id); TypeScript [`audiences.get()`](https://openemail.uk/docs/sdk/reference/audiences#get); Python [`audiences.get()`](https://openemail.uk/docs/python/reference/audiences#get); CLI [`openemail audiences get`](https://openemail.uk/docs/cli/reference/audiences#audiences-get).

### `audiences.create`

Create an audience

```ruby
create(body = nil, api_key: nil, **fields) -> Hash
```

Makes a new, empty audience in the workspace and returns it with its id. `name` is trimmed and must then be 1 to 120 characters. `description` is optional free text for whoever reads the list later.

The audience comes back with `builtin` nil and `contactCount` zero. Only the default audience carries a `builtin`, and it cannot be made or copied here.

Names are not checked for duplicates, so two calls with the same name make two audiences. Fill it with `audiences.add_contact`, one contact at a time, and the contacts must already be in the book.

Scopes: `audiences:write`.

**Parameters**

- `name` (`String`, required): Display name, trimmed, 1 to 120 characters. Not unique.
- `description` (`String`): What the audience is for. Leave it out to create the audience without one.
- `api_key` (`String`): Overrides the client's API key for this call only.

**Returns**

A Hash for the new audience, with its `id`, `builtin` nil and `contactCount` zero.

**Example**

```ruby
audience = client.audiences.create(
  name: "Product updates",
  description: "Customers who asked to hear about releases"
)

client.audiences.add_contact(audience[:id], email: "grace@example.com")
```

**Notes**

- The SDK does not retry a create after a network failure, and nothing here deduplicates by name, so a retry after a lost response can leave two audiences. List them and delete the spare.
- A workspace holds at most 100 audiences. The 101st is refused with 422 `audience_limit_reached` on `name`.
- Audiences belong to the workspace, not to the key or the person who made them.

Also available in: API [`POST /audiences`](https://openemail.uk/docs/api/reference/audiences#post-audiences); TypeScript [`audiences.create()`](https://openemail.uk/docs/sdk/reference/audiences#create); Python [`audiences.create()`](https://openemail.uk/docs/python/reference/audiences#create); CLI [`openemail audiences create`](https://openemail.uk/docs/cli/reference/audiences#audiences-create).

### `audiences.update`

Rename an audience or change its description

```ruby
update(id, patch = nil, api_key: nil, **fields) -> Hash
```

Changes the fields you send and leaves the rest alone. A key you leave out keeps its stored value and an explicit nil clears it, so `description: nil` empties the description while an empty patch changes nothing.

The default audience can be renamed like any other, and renaming it does not change `builtin` or what it holds. `builtin` itself cannot be set, moved or cleared from here.

The id never changes, and membership is untouched.

Scopes: `audiences:write`.

**Parameters**

- `id` (`String`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`.
- `name` (`String`): New display name, trimmed, 1 to 120 characters.
- `description` (`String`): New description. `nil` clears it.
- `api_key` (`String`): Overrides the client's API key for this call only.

**Returns**

A Hash, the audience as it stands after the change, with the unchanged `id` and `builtin`.

**Example**

```ruby
audience = client.audiences.update("aud_9f2c4b7e1a0d63d84c5f2e7b", name: "Release notes")

puts audience[:name], audience[:updatedAt]
```

**Notes**

- The SDK retries this call after a network failure, since the same patch sent twice leaves the same audience.
- Renaming does not move anybody. Contacts stay in the audience under its new name.

Also available in: API [`PATCH /audiences/{id}`](https://openemail.uk/docs/api/reference/audiences#patch-audiences-id); TypeScript [`audiences.update()`](https://openemail.uk/docs/sdk/reference/audiences#update); Python [`audiences.update()`](https://openemail.uk/docs/python/reference/audiences#update); CLI [`openemail audiences update`](https://openemail.uk/docs/cli/reference/audiences#audiences-update).

### `audiences.delete`

Delete an audience and keep its contacts

```ruby
delete(id, api_key: nil) -> Hash
```

Deletes the audience and drops every membership it held, in one transaction. The contacts themselves are not touched: they stay in the address book, in the default audience, and in any other audience they were in.

The default audience cannot be deleted. The call is refused with 409 `audience_immutable` on `id`, because it is what makes the book readable as a list. Delete the contacts instead if you mean to empty it.

There is no undo, and a new audience with the same name comes back empty.

Scopes: `audiences:write`.

**Parameters**

- `id` (`String`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`. Not the default audience.
- `api_key` (`String`): Overrides the client's API key for this call only.

**Returns**

A Hash with `object` set to `audience`, `id` and `deleted` set to true.

**Example**

```ruby
removed = client.audiences.delete("aud_9f2c4b7e1a0d63d84c5f2e7b")

puts removed[:id], removed[:deleted]
```

**Notes**

- The SDK does not retry a delete. A 404 on your own second attempt after a lost response means the first one worked.
- An audience from another workspace is a 404, never a 403.
- To keep the audience and take its contacts out, use `empty`.

Also available in: API [`DELETE /audiences/{id}`](https://openemail.uk/docs/api/reference/audiences#delete-audiences-id); TypeScript [`audiences.delete()`](https://openemail.uk/docs/sdk/reference/audiences#delete); Python [`audiences.delete()`](https://openemail.uk/docs/python/reference/audiences#delete); CLI [`openemail audiences delete`](https://openemail.uk/docs/cli/reference/audiences#audiences-delete).

### `audiences.empty`

Take every contact out of an audience and keep the audience

```ruby
empty(id, api_key: nil) -> Hash
```

Removes every membership of the audience in one transaction and returns the audience as it now stands, `contactCount` 0, plus `removed`, the number of contacts taken out. The audience keeps its id, name and description, so anything that points at it still works. The contacts are not touched: each one stays in the address book, in the default audience and in every other audience it is in.

The default audience cannot be emptied. The call is refused with 409 `audience_immutable` on `id`, because that audience holds every contact for as long as it is a contact.

There is no undo. The app asks the person to confirm before it empties a list, but this call asks nothing: it needs no verification code, whether it is made with an API key or with the OAuth access token of an app the person connected. Check the id before you call it.

Scopes: `audiences:write`.

**Parameters**

- `id` (`String`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`. Not the default audience.
- `api_key` (`String`): Overrides the client's API key for this call only.

**Returns**

A Hash, the audience as `get` returns it with `contactCount` 0, plus `removed`, the number of contacts taken out.

**Example**

```ruby
emptied = client.audiences.empty("aud_9f2c4b7e1a0d63d84c5f2e7b")

puts "#{emptied[:removed]} removed from #{emptied[:name]}"
```

**Notes**

- The SDK does not retry this call after a network failure. Emptying twice leaves the same empty audience, but the second call reports `removed: 0`, so read the audience with `get` if a response was lost.
- Emptying an audience that is already empty succeeds with `removed: 0`.
- To take out only some contacts, use `remove_contacts`. To delete the audience as well, use `delete`, which also leaves the contacts alone.

Also available in: API [`POST /audiences/{id}/empty`](https://openemail.uk/docs/api/reference/audiences#post-audiences-id-empty); TypeScript [`audiences.empty()`](https://openemail.uk/docs/sdk/reference/audiences#empty); Python [`audiences.empty()`](https://openemail.uk/docs/python/reference/audiences#empty); CLI [`openemail audiences empty`](https://openemail.uk/docs/cli/reference/audiences#audiences-empty).

### `audiences.list_contacts`

List one page of the contacts in one audience

```ruby
list_contacts(id, q: nil, source: nil, sort: nil, statuses: nil, limit: nil, cursor: nil, api_key: nil) -> OpenEmail::Page
```

Returns one page of the contacts in the audience. The rows are the contacts themselves, not membership records, so they carry `email`, `name`, `source`, `notes`, `lastSeenAt`, `createdAt` and `updatedAt`, plus `addedAt`, the date the contact joined this audience, and `unsubscribedAt`, when it unsubscribed from a broadcast sent to this audience, or nil while it is subscribed. An unsubscribed contact stays in the audience and broadcasts to it skip it.

By default the order is the one `contacts.list` uses: the contacts most recently written to first, and those never written to last. `sort:` picks another order, `q:` searches names and addresses, `source:` keeps only contacts saved by hand or only those recorded from the composer, and `statuses:` keeps only the subscribed or only the unsubscribed. These are the controls on the audience page in the app.

Paging is keyset: `limit:` takes 1 to 200 and defaults to 50, and `next_cursor` goes back as `cursor:` while `has_more?` is true, so every contact in the audience is reachable however large it grows. Send the same `q:`, `source:`, `sort:` and `statuses:` with every page. `list_all_contacts` and `iterate_contacts` do that walk for you, which is also how to export an audience.

Reading the default audience here returns the whole address book.

Scopes: `audiences:read`.

**Parameters**

- `id` (`String`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`.
- `limit` (`Integer`): Rows per page, a whole number from 1 to 200. The server defaults to 50.
- `cursor` (`String`): The `next_cursor` from the previous page. Never build one yourself. One that names no contact in this audience is a 400 `invalid_cursor`.
- `q` (`String`): Narrows to contacts whose name or address matches, up to 200 characters. When nothing matches exactly the search allows for a typo instead.
- `source` (`String`): Narrows to contacts recorded that way: `manual` for one somebody saved, `auto` for one recorded by a send from the app composer.
- `sort` (`String`): How the page is ordered. `last-heard-newest`, the default, puts the contacts most recently written to first and those never written to last, `last-heard-oldest` reverses it, `added-newest` and `added-oldest` order by `addedAt`, and `name` is alphabetical without case, with a contact that has no name sorting by its address.
- `statuses` (`Array<String>`): Keeps only `subscribed` members, only `unsubscribed` ones, or both. Leave it out, or name both, for everyone in the audience. `OpenEmail::AUDIENCE_MEMBER_STATUSES` holds the values.
- `api_key` (`String`): Overrides the client's API key for this call only.

**Returns**

An `OpenEmail::Page` with `items`, `has_more?` and `next_cursor`. Each item is a contact Hash, as `contacts.list` returns it, plus `addedAt`, when the contact joined this audience, and `unsubscribedAt`, nil unless it unsubscribed from a broadcast.

**Example**

```ruby
page = client.audiences.list_contacts("aud_9f2c4b7e1a0d63d84c5f2e7b", limit: 200, sort: "added-newest")

page.items.each { |contact| puts "#{contact[:email]} joined #{contact[:addedAt]}" }
```

**Notes**

- Only `audiences:read` is needed. A key with that scope reads the contacts in an audience without holding `contacts:read`.
- A contact in no audience of your own is still in the default one, so nothing is invisible to this route.
- An audience from another workspace is a 404.
- An unknown `sort`, `source` or status is a 422 `invalid_parameter`.

Also available in: API [`GET /audiences/{id}/contacts`](https://openemail.uk/docs/api/reference/audiences#get-audiences-id-contacts); TypeScript [`audiences.listContacts()`](https://openemail.uk/docs/sdk/reference/audiences#listContacts); Python [`audiences.list_contacts()`](https://openemail.uk/docs/python/reference/audiences#listContacts); CLI [`openemail audiences list-contacts`](https://openemail.uk/docs/cli/reference/audiences#audiences-list-contacts).

### `audiences.list_all_contacts`

Collect every contact in one audience into one array

```ruby
list_all_contacts(id, q: nil, source: nil, sort: nil, statuses: nil, limit: nil, cursor: nil, api_key: nil) -> Array<Hash>
```

Walks every page of one audience and returns all of its contacts, each with `addedAt`, in the order `list_contacts` returns them. `q:`, `source:`, `sort:` and `statuses:` are sent with every page, so the walk returns the filtered list in the order you asked for. This is the way to export an audience. On the default audience that is the whole address book, so prefer `iterate_contacts` when you can stop early. `limit:` sets the page size of each request, not the total.

Scopes: `audiences:read`.

**Parameters**

- `id` (`String`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`.
- `limit` (`Integer`): Page size per request, from 1 to 200, defaulting to 50 on the server.
- `cursor` (`String`): A cursor from an earlier page to start after.
- `q` (`String`): Narrows to contacts whose name or address matches, up to 200 characters. When nothing matches exactly the search allows for a typo instead.
- `source` (`String`): Narrows to contacts recorded that way: `manual` for one somebody saved, `auto` for one recorded by a send from the app composer.
- `sort` (`String`): The order of the walk, as for `list_contacts`. Defaults to `last-heard-newest`.
- `statuses` (`Array<String>`): Keeps only `subscribed` members, only `unsubscribed` ones, or both. Leave it out, or name both, for everyone in the audience. `OpenEmail::AUDIENCE_MEMBER_STATUSES` holds the values.
- `api_key` (`String`): Overrides the client's API key for every page of this walk.

**Returns**

An Array of Hashes holding every contact in the audience that matches, each a contact Hash plus `addedAt` and `unsubscribedAt`.

**Example**

```ruby
contacts = client.audiences.list_all_contacts("aud_9f2c4b7e1a0d63d84c5f2e7b", limit: 200)

to = contacts.map { |contact| contact[:email] }

puts "#{to.size} recipients"
```

**Notes**

- A failure on any page raises out of the whole call, and the contacts already fetched are discarded.
- An audience from another workspace is a 404 on the first page.

Also available in: API [`GET /audiences/{id}/contacts`](https://openemail.uk/docs/api/reference/audiences#get-audiences-id-contacts); TypeScript [`audiences.listAllContacts()`](https://openemail.uk/docs/sdk/reference/audiences#listAllContacts); Python [`audiences.list_all_contacts()`](https://openemail.uk/docs/python/reference/audiences#listAllContacts).

### `audiences.iterate_contacts`

Stream the contacts in one audience one at a time

```ruby
iterate_contacts(id, q: nil, source: nil, sort: nil, statuses: nil, limit: nil, cursor: nil, api_key: nil, &block) -> Enumerator<Hash>
```

Returns an Enumerator over one audience that yields contacts individually, each with `addedAt`, or yields each one to a block when given one, and fetches the next page only when the current one is drained. `q:`, `source:`, `sort:` and `statuses:` are sent with every page. Nothing is requested until you consume it, and breaking out of the loop stops further requests, which suits a send loop or an export that works through a large audience.

Scopes: `audiences:read`.

**Parameters**

- `id` (`String`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`.
- `limit` (`Integer`): Page size per request, from 1 to 200, defaulting to 50 on the server.
- `cursor` (`String`): A cursor from an earlier page to start after.
- `q` (`String`): Narrows to contacts whose name or address matches, up to 200 characters. When nothing matches exactly the search allows for a typo instead.
- `source` (`String`): Narrows to contacts recorded that way: `manual` for one somebody saved, `auto` for one recorded by a send from the app composer.
- `sort` (`String`): The order of the walk, as for `list_contacts`. Defaults to `last-heard-newest`.
- `statuses` (`Array<String>`): Keeps only `subscribed` members, only `unsubscribed` ones, or both. Leave it out, or name both, for everyone in the audience. `OpenEmail::AUDIENCE_MEMBER_STATUSES` holds the values.
- `api_key` (`String`): Overrides the client's API key for every page of this walk.

**Returns**

An Enumerator of Hashes, one contact per step, each a contact Hash plus `addedAt` and `unsubscribedAt`, or yields each one to a block.

**Example**

```ruby
slug = "welcome"

client.audiences.iterate_contacts("aud_9f2c4b7e1a0d63d84c5f2e7b") do |contact|
  client.templates.send(slug, from: "news@acme.com", to: contact[:email])
end
```

**Notes**

- The Enumerator is lazy, so an abandoned loop costs only the pages you consumed.

Also available in: API [`GET /audiences/{id}/contacts`](https://openemail.uk/docs/api/reference/audiences#get-audiences-id-contacts); TypeScript [`audiences.iterateContacts()`](https://openemail.uk/docs/sdk/reference/audiences#iterateContacts); Python [`audiences.iterate_contacts()`](https://openemail.uk/docs/python/reference/audiences#iterateContacts).

### `audiences.add_contact`

Put a contact in an audience

```ruby
add_contact(id, body = nil, api_key: nil, **fields) -> Hash
```

Adds one contact to one audience and returns the membership, with the contact it points at. The address is trimmed and lower cased before the lookup, and it has to be a contact in this workspace already: an address that is not in the book is refused with 422 `contact_not_found` on `email`. Save it with `contacts.create` first.

There is one membership per audience and contact, so adding somebody who is already in the audience returns the membership that is there, with its original `addedAt`, rather than adding a second one or failing. That makes the call safe to replay, and the SDK retries it after a network failure.

Adding to the default audience is accepted and changes nothing, since every contact is already in it.

Scopes: `audiences:write`.

**Parameters**

- `id` (`String`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`.
- `email` (`String`, required): The address of a contact already in the workspace book, matched case insensitively.
- `api_key` (`String`): Overrides the client's API key for this call only.

**Returns**

A Hash with `object` set to `audience_member`, `audienceId`, `addedAt` and `contact`.

**Example**

```ruby
client.contacts.create(email: "grace@example.com", name: "Grace Hopper")

membership = client.audiences.add_contact("aud_9f2c4b7e1a0d63d84c5f2e7b", email: "grace@example.com")

puts membership[:audienceId], membership[:addedAt]
```

**Notes**

- To add up to 200 contacts in one call, use `add_contacts`. To create contacts as you add them, use `import_contacts`.
- `audiences:write` is the only scope checked. Adding a contact to an audience is not a write to the contact.
- Deleting the contact later drops this membership with it.

Also available in: API [`POST /audiences/{id}/contacts`](https://openemail.uk/docs/api/reference/audiences#post-audiences-id-contacts); TypeScript [`audiences.addContact()`](https://openemail.uk/docs/sdk/reference/audiences#addContact); Python [`audiences.add_contact()`](https://openemail.uk/docs/python/reference/audiences#addContact); CLI [`openemail audiences add-contact`](https://openemail.uk/docs/cli/reference/audiences#audiences-add-contact).

### `audiences.remove_contact`

Take a contact out of an audience

```ruby
remove_contact(id, email, api_key: nil) -> Hash
```

Drops one membership and leaves everything else alone. The contact stays in the address book, in the default audience and in every other audience it was in. The address is trimmed and lower cased, and the SDK URL encodes it for the path.

A contact that is not in this audience is a 404, so a typo cannot report a removal that never happened.

The default audience cannot be thinned. Removing a contact from it is refused with 409 `audience_immutable`, because it holds every contact by definition. Use `contacts.delete` when you mean the contact to go.

Scopes: `audiences:write`.

**Parameters**

- `id` (`String`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`. Not the default audience.
- `email` (`String`, required): The contact's address, matched case insensitively.
- `api_key` (`String`): Overrides the client's API key for this call only.

**Returns**

A Hash with `object` set to `audience_member`, `audienceId`, `email` and `deleted` set to true.

**Example**

```ruby
removed = client.audiences.remove_contact("aud_9f2c4b7e1a0d63d84c5f2e7b", "grace@example.com")

puts removed[:email], removed[:deleted]
```

**Notes**

- The SDK does not retry a delete. A 404 on your own second attempt after a lost response means the first one worked.
- Removing the last contact leaves the audience in place and empty.

Also available in: API [`DELETE /audiences/{id}/contacts/{email}`](https://openemail.uk/docs/api/reference/audiences#delete-audiences-id-contacts-email); TypeScript [`audiences.removeContact()`](https://openemail.uk/docs/sdk/reference/audiences#removeContact); Python [`audiences.remove_contact()`](https://openemail.uk/docs/python/reference/audiences#removeContact); CLI [`openemail audiences remove-contact`](https://openemail.uk/docs/cli/reference/audiences#audiences-remove-contact).

### `audiences.add_contacts`

Put up to 200 existing contacts in an audience in one call

```ruby
add_contacts(id, body = nil, api_key: nil, **fields) -> Hash
```

Adds many contacts to one audience in one transaction and reports what happened to each address. Each address is trimmed and lower cased, a repeat counts once, and each has to be a contact in this workspace already.

Nothing is refused for one address. An address that is not a contact is returned in `missing` and the others are still added, and a contact that is in the audience already is counted in `unchanged` and keeps its original `addedAt`. `added` counts the contacts that joined in this call.

This never creates a contact. To save new addresses and put them in the audience in the same call, use `import_contacts`. Adding to the default audience succeeds with `added: 0`, since every contact is already in it.

Scopes: `audiences:write`.

**Parameters**

- `id` (`String`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`.
- `emails` (`Array<String>`, required): From 1 to 200 addresses of contacts already in the workspace book, matched case insensitively. An empty Array or more than 200 is a 422 `invalid_parameter` on `emails`, and an empty or overlong address is the same error on that entry, such as `emails.2`.
- `api_key` (`String`): Overrides the client's API key for this call only.

**Returns**

A Hash with `object` set to `audience_batch`, `audienceId`, `added`, `unchanged` and `missing`. `missing` lists the addresses that are not contacts, trimmed, lower cased and once each.

**Example**

```ruby
result = client.audiences.add_contacts(
  "aud_9f2c4b7e1a0d63d84c5f2e7b",
  emails: ["grace@example.com", "ada@example.com", "nobody@example.com"]
)

puts result[:added], result[:unchanged]

puts "not contacts yet: #{result[:missing].join(", ")}" if result[:missing].any?
```

**Notes**

- Safe to replay: a second call with the same addresses adds nothing and reports them as `unchanged`. The SDK retries it after a network failure.
- Send a longer list in chunks of 200.
- `audiences:write` is the only scope checked. Adding contacts to an audience is not a write to the contacts.
- An audience from another workspace is a 404 `audience_not_found`.

Also available in: API [`POST /audiences/{id}/contacts/batch`](https://openemail.uk/docs/api/reference/audiences#post-audiences-id-contacts-batch); TypeScript [`audiences.addContacts()`](https://openemail.uk/docs/sdk/reference/audiences#addContacts); Python [`audiences.add_contacts()`](https://openemail.uk/docs/python/reference/audiences#addContacts); CLI [`openemail audiences add-contacts`](https://openemail.uk/docs/cli/reference/audiences#audiences-add-contacts).

### `audiences.remove_contacts`

Take up to 200 contacts out of an audience in one call

```ruby
remove_contacts(id, body = nil, api_key: nil, **fields) -> Hash
```

Removes many contacts from one audience in one transaction and reports what happened to each address. Each address is trimmed and lower cased and a repeat counts once. The contacts stay in the address book, in the default audience and in every other audience they are in.

Nothing is refused for one address, which is where this differs from `remove_contact`. A contact that is not in this audience is returned in `notInAudience`, an address that is not a contact at all in `missing`, and the rest are still removed. `removed` counts the contacts taken out in this call.

The default audience cannot be thinned. The call is refused with 409 `audience_immutable` on `id`, because that audience holds every contact by definition. Use `contacts.delete` when you mean a contact to go.

Scopes: `audiences:write`.

**Parameters**

- `id` (`String`, required): Audience id such as `aud_9f2c4b7e1a0d63d84c5f2e7b`. Not the default audience.
- `emails` (`Array<String>`, required): From 1 to 200 addresses, matched case insensitively. An empty Array or more than 200 is a 422 `invalid_parameter` on `emails`, and an empty or overlong address is the same error on that entry, such as `emails.2`.
- `api_key` (`String`): Overrides the client's API key for this call only.

**Returns**

A Hash with `object` set to `audience_batch`, `audienceId`, `removed`, `notInAudience` and `missing`.

**Example**

```ruby
result = client.audiences.remove_contacts(
  "aud_9f2c4b7e1a0d63d84c5f2e7b",
  emails: ["grace@example.com", "ada@example.com"]
)

puts "#{result[:removed]} removed, #{result[:notInAudience].size} were not in it"
```

**Notes**

- Safe to replay, and the SDK retries it after a network failure. A replay after a lost response succeeds and reports the contacts the first call removed under `notInAudience`, so `removed` on the retry can read 0.
- Send a longer list in chunks of 200. To take everyone out, use `empty`.
- Removing the last contacts leaves the audience in place and empty.

Also available in: API [`POST /audiences/{id}/contacts/batch-remove`](https://openemail.uk/docs/api/reference/audiences#post-audiences-id-contacts-batch-remove); TypeScript [`audiences.removeContacts()`](https://openemail.uk/docs/sdk/reference/audiences#removeContacts); Python [`audiences.remove_contacts()`](https://openemail.uk/docs/python/reference/audiences#removeContacts); CLI [`openemail audiences remove-contacts`](https://openemail.uk/docs/cli/reference/audiences#audiences-remove-contacts).

### `audiences.import_contacts`

Import up to 500 addresses into an audience, creating contacts as needed

```ruby
import_contacts(id, body = nil, api_key: nil, **fields) -> Hash
```

Does what the CSV import on an audience page does, without the file. Each row is an address and an optional name, and the whole call runs 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 as every new contact does. An address that is a contact already is reused as it stands: its name is kept, and a name sent here only fills one that is empty. Every imported contact ends up in this audience. Importing an address whose contact was deleted brings it back.

A row whose address is not well formed is skipped rather than failing the call. It is counted in `skipped` and its address is returned in `invalid` exactly as you sent it, and every other row is still imported. Rows with the same address count once.

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

**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.
- `contacts` (`Array<Hash>`, required): From 1 to 500 rows, each a Hash with `email` and `name`. `email` is required, at most 320 characters, and trimmed and lower cased. `name` is optional, at most 200 characters, and used for a new contact or for an existing one whose name is empty. An empty Array, more than 500 rows, an empty `email` or any other key in a row is a 422.
- `api_key` (`String`): Overrides the client's API key for this call only.

**Returns**

A Hash with `object` set to `audience_import`, `audienceId`, `created`, `added`, `skipped` and `invalid`. `created` counts new contacts, `added` the contacts that joined this audience in this call, new and existing together.

**Example**

```ruby
rows = [
  {email: "grace@example.com", name: "Grace Hopper"},
  {email: "ada@example.com"},
  {email: "not an address"}
]

result = client.audiences.import_contacts("aud_9f2c4b7e1a0d63d84c5f2e7b", contacts: rows)

p result[:created], result[:added], result[:invalid]
```

**Notes**

- Needs both `audiences:write` and `contacts:write`, because it creates contacts as well as memberships. A key without either is refused with 403 `insufficient_scope` before anything is saved.
- Safe to replay: running the same rows again creates nothing twice and adds nothing twice. The SDK retries it after a network failure.
- Send a longer list in chunks of 500.
- To add contacts that already exist without creating any, use `add_contacts`, which reports the addresses that are not contacts instead of saving them.

Also available in: API [`POST /audiences/{id}/import`](https://openemail.uk/docs/api/reference/audiences#post-audiences-id-import); TypeScript [`audiences.importContacts()`](https://openemail.uk/docs/sdk/reference/audiences#importContacts); Python [`audiences.import_contacts()`](https://openemail.uk/docs/python/reference/audiences#importContacts); CLI [`openemail audiences import-contacts`](https://openemail.uk/docs/cli/reference/audiences#audiences-import-contacts).
