---
title: "Audiences"
description: "`audiences.list`, `get`, `create`, `update`, `delete`, `empty`, `growth`, `list_contacts`, `add_contact`, `add_contacts`, `import_contacts`, `remove_contact` and `remove_contacts`."
url: "https://openemail.uk/docs/python/audiences"
area: "Python"
category: "Mailbox"
---

# Audiences

`audiences.list`, `get`, `create`, `update`, `delete`, `empty`, `growth`, `list_contacts`, `add_contact`, `add_contacts`, `import_contacts`, `remove_contact` and `remove_contacts`.

## Every method

**audiences.py**

```
from openemail import openemail

audiences = openemail.audiences.list_all()
everyone = next((audience for audience in audiences if audience['builtin'] == 'default'), None)

created = openemail.audiences.create({
    'name': 'Product updates',
    'description': 'Customers who asked to hear about releases',
})
audience_id = created['id']

openemail.contacts.create({'email': 'grace@example.com', 'name': 'Grace Hopper'})
openemail.audiences.add_contact(audience_id, {'email': 'grace@example.com'})

bulk = openemail.audiences.add_contacts(audience_id, {
    'emails': ['ada@example.com', 'alan@example.com'],
})

imported = openemail.audiences.import_contacts(audience_id, {
    'contacts': [{'email': 'katherine@example.com', 'name': 'Katherine Johnson'}],
})

members = openemail.audiences.list_all_contacts(
    audience_id,
    q='grace',
    sort='added-newest',
    limit=200,
)

growth = openemail.audiences.growth(audience_ids=[audience_id], days=30)

openemail.audiences.update(audience_id, {'name': 'Release notes'})
openemail.audiences.remove_contact(audience_id, 'grace@example.com')
openemail.audiences.remove_contacts(audience_id, {'emails': ['ada@example.com']})
openemail.audiences.empty(audience_id)
openemail.audiences.delete(audience_id)

print(everyone['contactCount'] if everyone else None, bulk['missing'], imported['created'])
print(len(members), growth['totals']['added'])
```

An audience is 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. Branch on `builtin` rather than on the name, which anybody can change.

Send to one or more audiences with `openemail.broadcasts.send`, on the Broadcasts page. Putting a contact in an audience is a write to the audience rather than to the contact, so `audiences:write` is the only scope checked. `import_contacts` is the exception: it creates contacts, so it needs `contacts:write` as well.

- [Broadcasts](https://openemail.uk/docs/python/broadcasts.md): Send one message to everybody in one or more audiences.

> `add_contact` takes an address that is already a contact and refuses one that is not, with 422 `contact_not_found`. Save it with `openemail.contacts.create` first. Adding somebody twice answers with the membership that is already there, carrying its original `addedAt`, so the call is safe to retry.

> The default audience can be renamed and described like any other, but it cannot be deleted and it cannot be thinned. Both are refused with 409 `audience_immutable`. Delete the contact when you mean the contact to go.

## Response: AudienceResource

`list` returns one page of these, a dict with `items`, `hasMore` and `nextCursor`, the default audience first and the rest newest first, and `list_all` and `iterate` walk every page. `get`, `create` and `update` each return one. `list_contacts` returns a page of `AudienceContactResource` instead, the contacts themselves with the date each one joined rather than membership records, with `list_all_contacts` and `iterate_contacts` beside it.

- `id` (str): The durable handle, `aud_` followed by 24 hex characters. Names are not unique, so this is what belongs in stored configuration.
- `name` (str): Trimmed on write, 1 to 120 characters. Two audiences may share a name, because an audience is addressed by its id.
- `description` (str | None): Free text for whoever reads the list later. `None` when nobody wrote any, and an explicit `None` on `update` clears it.
- `builtin` (AudienceBuiltin | str | None): `default` on exactly one row per workspace, the audience that holds every contact, and `None` on every audience somebody created. The type stays open, with `str` beside the literal, so a built-in added later does not break code typed against this one.
- `contactCount` (int): How many contacts are in the audience, counted at the moment of the read rather than cached. Two reads either side of a `contacts.create` disagree by one.
- `lastContactAt` (str | None): ISO-8601 UTC, when the contact who joined most recently joined this audience. `None` while the audience is empty.
- `createdAt` (str): ISO-8601 UTC, when the audience was made. Fixes the list order after the default one.
- `updatedAt` (str): ISO-8601 UTC, bumped by a rename or a description change. Membership changes do not touch it.

## Parameters: audiences.list_contacts

- `limit` (int): How many contacts per page: an integer from 1 to 200, defaulting to 50.
- `cursor` (str): The `nextCursor` from the previous page, sent with the same `q`, `source` and `sort`. A cursor naming a contact that is not in this audience is a 400 `invalid_cursor`.
- `q` (str): Searches the name and the address, up to 200 characters. When nothing matches exactly on the first page, close spellings are returned instead, and the pages that follow keep matching the same way.
- `source` (ContactSource): `'manual'` for the contacts somebody saved on purpose, `'auto'` for the ones the app composer recorded. Leave it out for everyone in the audience.
- `sort` (AudienceMemberSort): `'last-heard-newest'` (the default) and `'last-heard-oldest'` go by `lastSeenAt`, and contacts that have never been mailed come last in the first and first in the second. `'added-newest'` and `'added-oldest'` go by when each contact joined this audience, and `'name'` ignores case and sorts a contact with no name by its address.
- `statuses` (Sequence[AudienceMemberStatus]): `['subscribed']` keeps the members who have not unsubscribed and `['unsubscribed']` the ones who have. Leave it out, or name both, for everyone in the audience. `AUDIENCE_MEMBER_STATUSES` holds the values.

## Response: AudienceContactResource

`list_contacts` returns a `Page[AudienceContactResource]`, and `list_all_contacts` and `iterate_contacts` walk every page with the same options. Each row is a `ContactResource`, whose fields are on the Contacts page, with two more. Walking every page is how to export an audience.

- `addedAt` (str): ISO-8601 UTC, when the contact joined this audience. Taking a contact out and adding it again starts it afresh.
- `unsubscribedAt` (str | None): ISO-8601 UTC, when the contact unsubscribed from a broadcast sent to this audience, or `None` while it is subscribed. An unsubscribed contact stays in the audience, and broadcasts to it skip it. Taking it out and adding it again makes it subscribed afresh.

## Adding and removing in bulk

`add_contacts` and `remove_contacts` take `{'emails': [...]}`, 1 to 200 addresses, and change one audience in one request. `add_contacts` never creates a contact: an address that is not one comes back in `missing`, and `import_contacts` is the call that creates them. Both are safe to repeat, so a retry after a timeout reports the same people as already done rather than failing.

> Adding to the default audience answers `'added': 0`, because every contact is in it already, and `remove_contacts` on it is refused with 409 `audience_immutable`. Taking somebody out of an audience leaves them in the address book, in the default audience and in their other audiences.

- `audienceId` (str): The audience the call changed, on both results.
- `added` (int): On `AudienceBatchAddResource`: the new memberships this call made.
- `unchanged` (int): On `AudienceBatchAddResource`: contacts that were in the audience already. Nothing was written for them.
- `removed` (int): On `AudienceBatchRemoveResource`: the memberships this call took away.
- `notInAudience` (list[str]): On `AudienceBatchRemoveResource`: contacts that were not in the audience, so nothing happened to them.
- `missing` (list[str]): On both: the addresses that are not contacts in this workspace, lowercased and without repeats.

## Importing

`import_contacts` is the CSV import on the audience page. It takes `{'contacts': [...]}`, 1 to 500 rows, each with an `email` and an optional `name`: each well-formed address becomes a contact if it is not one yet, and every one lands in the audience. Send a longer list in several calls. It needs `audiences:write` and `contacts:write`.

> An address that is already a contact is reused and keeps its name, and a `name` here only fills one that was empty. A new contact is saved as `manual` and joins the default audience too, and an address that was deleted from the book comes back. Replaying the same rows creates nothing twice.

- `audienceId` (str): The audience the rows went into.
- `created` (int): New contacts this call saved.
- `added` (int): New memberships in this audience, counting contacts that existed already and were not in it yet.
- `skipped` (int): Rows that were not imported because the address was malformed.
- `invalid` (list[str]): The malformed addresses, exactly as they were sent.

## Emptying

`empty(id)` takes every contact out of one audience in one request and returns an `EmptiedAudienceResource`: the audience as it now stands, with `contactCount` at 0, plus `removed`, the number of memberships taken away. The audience keeps its id, name and description, and every contact stays in the address book and in its other audiences.

> It cannot be undone and nothing records who was in the list, so walk `list_all_contacts` first if you may want it back. The default audience cannot be emptied, and the call is refused with 409 `audience_immutable`.

## Growth

`growth()` reads how many contacts joined each audience over a window that ends now, by day, hour or minute, which is the chart on the audiences page. It needs `audiences:read` and returns an `AudienceGrowthResource`.

> An audience records when somebody joined and never when they left, so every join figure counts the people still in the list today by the date they joined, and a line never falls. A contact who joined and later left is in none of the numbers.

**Parameters**

- `audience_ids` (Sequence[str]): Up to 50 audience ids, sent joined with commas. Leave it out for every audience. An id that is not an audience in this workspace is a 404 `audience_not_found`.
- `days` (int): How far back the window reaches, 1 to 1095. It is 30 when neither `days` nor `minutes` is given.
- `minutes` (int): The window in minutes, 1 to 1576800, for a window shorter than a day. It wins over `days` when both are given.
- `grain` (TrackingGrain): The size of each bucket: `day` (the default), `hour` or `minute`.
- `offset_minutes` (int): The viewer's offset from UTC in minutes, -840 to 840, so day and hour buckets start at their local boundary. 0 by default.

**Response**

- `since` (str): ISO-8601 UTC, the start of the first bucket.
- `until` (str): ISO-8601 UTC, the moment of the read.
- `totals` (AudienceGrowthTotals): `contacts` counts each person once however many lists they are in, and `memberships` adds the lists up, so a person counts once for every list read that holds them. `subscribed` counts, once each, the people still subscribed to at least one of the lists read. `added` sums the joins in the window, `unsubscribed` the unsubscribes in it, `lists` is how many audiences were read, and `busiest` is the bucket with the most joins, or `None`.
- `series` (list[AudienceGrowthSeries]): One entry per audience, largest first: `id`, `name`, `builtin` (`True` on the default audience), `total` members now, `subscribed` (those of them who have not unsubscribed), `before` (those who joined before `since`), `added` (those who joined inside the window), `unsubscribed` (those who unsubscribed inside it) and `buckets`, each a dict of `bucket`, `added` and `unsubscribed`. Only buckets with a join or an unsubscribe are listed, keyed `YYYY-MM-DD`, `YYYY-MM-DDTHH` or `YYYY-MM-DDTHH:MM` in the offset's local time.

## Reference

- [`audiences.list()`](https://openemail.uk/docs/python/reference/audiences#list): full reference
- [`audiences.list_all()`](https://openemail.uk/docs/python/reference/audiences#listAll): full reference
- [`audiences.iterate()`](https://openemail.uk/docs/python/reference/audiences#iterate): full reference
- [`audiences.get()`](https://openemail.uk/docs/python/reference/audiences#get): full reference
- [`audiences.create()`](https://openemail.uk/docs/python/reference/audiences#create): full reference
- [`audiences.update()`](https://openemail.uk/docs/python/reference/audiences#update): full reference
- [`audiences.delete()`](https://openemail.uk/docs/python/reference/audiences#delete): full reference
- [`audiences.empty()`](https://openemail.uk/docs/python/reference/audiences#empty): full reference
- [`audiences.growth()`](https://openemail.uk/docs/python/reference/audiences#growth): full reference
- [`audiences.list_contacts()`](https://openemail.uk/docs/python/reference/audiences#listContacts): full reference
- [`audiences.list_all_contacts()`](https://openemail.uk/docs/python/reference/audiences#listAllContacts): full reference
- [`audiences.iterate_contacts()`](https://openemail.uk/docs/python/reference/audiences#iterateContacts): full reference
- [`audiences.add_contact()`](https://openemail.uk/docs/python/reference/audiences#addContact): full reference
- [`audiences.add_contacts()`](https://openemail.uk/docs/python/reference/audiences#addContacts): full reference
- [`audiences.import_contacts()`](https://openemail.uk/docs/python/reference/audiences#importContacts): full reference
- [`audiences.remove_contact()`](https://openemail.uk/docs/python/reference/audiences#removeContact): full reference
- [`audiences.remove_contacts()`](https://openemail.uk/docs/python/reference/audiences#removeContacts): full reference
