# List broadcasts

`GET /v1/email/broadcasts`

Returns a paginated list of broadcasts in the workspace, newest first. `created_after` and `created_before` narrow the list to broadcasts created in a half-open window, which is how you page a single month or quarter rather than the whole history.

## Code samples

### TypeScript

```ts
for await (const broadcast of bird.broadcasts.list({ status: ["sent"] })) {
  console.log(broadcast.id, broadcast.status);
}
```

### Python

```py
for broadcast in client.broadcasts.list(status=["sent"]):
    print(broadcast.id, broadcast.status)
```

### Go

```go
for broadcast, err := range client.Broadcasts.List(context.Background(),
	bird.BroadcastsListParams{Status: []bird.EmailBroadcastStatus{bird.EmailBroadcastStatusSent}}) {
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(*broadcast.Id, *broadcast.Status)
}
```

### PHP

```php
foreach ($bird->broadcasts->list(['status' => ['sent']]) as $broadcast) {
    echo $broadcast->getId(), ' ', $broadcast->getStatus(), PHP_EOL;
}
```

### CLI

```sh
bird email broadcasts list
```

### cURL

```sh
curl -X GET "https://us1.platform.bird.com/v1/email/broadcasts" \
  -H "Authorization: Bearer $TOKEN" \
  --url-query "limit=25"
```

## Example response `200`

```json
{
  "data": [
    {
      "id": "eb_01krdgeqcxet5s7t44vh8rt9mg",
      "from": {
        "email": "jane@acme.com",
        "name": "Jane Doe"
      },
      "audience_id": "adn_01krdgeqcxet5s7t44vh8rt9mg",
      "template": {
        "id": "emt_01krdgeqcxet5s7t44vh8rt9mg",
        "version_id": "emv_01krdgeqcxet5s7t44vh8rt9mg"
      },
      "category": "marketing",
      "reply_to": [
        {
          "email": "jane@acme.com",
          "name": "Jane Doe"
        }
      ],
      "status": "sent",
      "failure_reason": null,
      "failure_detail": null,
      "recipient_count": 4820,
      "sent_count": 4820,
      "delivered_count": 4712,
      "bounced_count": 96,
      "complained_count": 12,
      "open_count": 3104,
      "click_count": 812,
      "unique_opens_non_prefetched": 2140,
      "unique_clicks": 693,
      "out_of_band_bounces": 14,
      "delivered_recipients": 4724,
      "tags": [
        {
          "name": "category",
          "value": "welcome"
        }
      ],
      "track_opens": true,
      "track_clicks": true,
      "created_at": "2026-09-01T09:14:02.418Z",
      "scheduled_at": "2026-09-02T08:00:00Z",
      "started_at": "2026-09-02T08:00:03.771Z",
      "sent_at": "2026-09-02T08:11:47.902Z"
    }
  ],
  "next_cursor": "eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE0OjAzOjEwWlwiIiwiaSI6IjAxOTJmM2IxLTRjN2UtN2EyYi05ZDYxLThmM2E1YzJlN2I0MCJ9",
  "prev_cursor": null,
  "refresh_cursor": "eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE2OjQyOjAxWlwiIiwiaSI6IjAxOTJmM2IxLTllMDQtN2NkMy1iODE3LTJhNmY0ZDFjOGUwOSJ9"
}
```

## Query parameters

- `limit` (integer): Maximum number of items to return per page.
- `starting_after` (string): Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order.
- `ending_before` (string): Cursor from the `prev_cursor` or `refresh_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. `prev_cursor` returns the preceding page. `refresh_cursor` anchors at the first row of that response, which on a newest-first sort is how to fetch the items that have appeared since.
- `status` (array): Filter by lifecycle status. Repeat the parameter to match more than one status, for example `?status=accepted&status=sending`.
- `audience_id` (string): Filter by audience. Only broadcasts that use this audience are returned.
- `tag` (string): Filter by tag. Pass `name` to match any broadcast that has that tag name, or pass `name:value` to match a specific tag pair, for example `campaign:spring_launch`.
- `q` (string): Case-insensitive substring match against the broadcast's tag names and values, or the referenced template's name.
- `created_after` (string): Limits the response to resources created at or after this timestamp. Combine it with `created_before` to select a time window. Use an RFC 3339 timestamp with a timezone offset.
- `created_before` (string): Limits the response to resources created before this timestamp. Combine it with `created_after` to select a time window. Use an RFC 3339 timestamp with a timezone offset.

## Response body

- `data` (array of object, required): Page of broadcast objects.
- `data.id` (string, required): Broadcast ID.
- `data.from` (object): The address this broadcast sends from. `name` is filled in when the broadcast was given a display name to send under. Left out on a draft that has not picked a sender yet.
- `data.from.email` (string, required): Email address.
- `data.from.name` (string): Display name shown alongside the address in mail clients.
- `data.audience_id` (string): The audience this broadcast sends to. When the send starts we turn the audience into a list of recipients, and you can read that list a page at a time with [List recipients of a broadcast](/docs/api/reference/list-email-broadcast-recipients). Left out on a draft that has not picked an audience yet.
- `data.template` (nullable object): The template this broadcast sends. A broadcast sends the template's published version, and the exact version is fixed when the broadcast is prepared for sending, so publishing a new version afterwards does not change what this broadcast sends. Null on a draft that has not chosen a template yet.
- `data.template.id` (string, required): Which template the broadcast sends. Which version of it the send is fixed to is `version_id`.
- `data.template.version_id` (nullable string): The template version this broadcast is fixed to. It is chosen when the broadcast is prepared for sending, so publishing a new version while the broadcast is going out cannot change what the rest of the recipients get. Null until the broadcast is prepared.
- `data.html_bytes` (integer): Size of the HTML body this broadcast sends, in bytes, or 0 when its content has no HTML part. Measured on the template version the broadcast sends, so this is the real body we send and differs per recipient only by that recipient's own merge values. Returned on a single broadcast read, and absent from the list and from the broadcast that creating, updating, sending or canceling one returns, none of which measure the content. Absent too when the broadcast has no template or its content can no longer be read.
- `data.text_bytes` (integer): Size of the plain-text body this broadcast sends, in bytes, or 0 when its content has no plain-text part. Measured, and absent, the same way as `html_bytes`.
- `data.category` (string, required)

  What kind of email this is, which decides how suppressions apply to it. A `marketing` broadcast is held back from every suppressed address. A `transactional` one still goes to addresses suppressed for a complaint or an unsubscribe, because those suppressions are about marketing mail.

  Possible values: `marketing`, `transactional`
- `data.ip_pool_id` (string): The IP pool this broadcast sends from, or `ipp_shared` when it sends through the shared pool. Absent when it sends on your organization's default pool.
- `data.reply_to` (array of object): Where replies to this broadcast go, if you want them somewhere other than the `from` address. Absent when you have not set one.
- `data.reply_to.email` (string, required): Email address.
- `data.reply_to.name` (string): Display name shown alongside the address in mail clients.
- `data.headers` (object): Any custom email headers set on the broadcast. Returned on a single broadcast read and on the broadcast that creating, updating, sending or canceling one returns, and absent from the list. The unsubscribe headers we add ourselves are not included.
- `data.status` (string, required): Where the broadcast itself has got to, separate from what happened to individual recipients: for that, read `sent_count`, `delivered_count`, `bounced_count` and `complained_count` below. When it is `failed`, `failure_reason` says why.
- `data.next` (array of object)

  What to do next about this broadcast, given the state it is in. Each entry names one action and
  says why it is worth taking. Present on reads that compute it: an empty list means there is
  nothing to do, and the field is absent entirely on responses that do not report next actions.
- `data.next.kind` (string, required)

  What you do about this step.

  - `operation`: call the operation named in `operation`, then
    read again.
  - `external`: act somewhere this API does not reach, then read
    again.
  - `wait`: nothing is asked of you, so read again later.
  - `terminal`: nothing you do resolves this, so stop retrying.

  Tolerate a value you do not recognize: show the `description` and
  offer no action.

  Possible values (may grow over time): `operation`, `external`, `wait`, `terminal`
- `data.next.description` (string, required): A short, human-readable label for the step, suitable for display.
- `data.next.operation` (string): The operationId to call. Present only when `kind` is `operation`. The operation's own schema says how to call it; this says only which one, and what to address it with.
- `data.next.params` (object): The parameters that address the operation, by name: `{"sender_id": "…"}` for an operation on `/v1/sms/senders/{sender_id}/requirements`. A parameter the operation takes in its query string is given the same way, so an operation addressed as `?subject_id=` carries `{"subject_id": "…"}`. Every parameter the call needs is here, whether its value came from the thing you were acting on or is fixed for this step, so you can make the call from this object alone. Present only when `kind` is `operation` and the operation names a subject. A request body, when the operation takes one, is described by the operation's own schema and never appears here.
- `data.next.url` (string): A URL to open. Present only when `kind` is `external`, and only when the step has one. An external step whose `description` says to go and do something with no URL to open is normal.
- `data.failure_reason` (nullable string)

  Why the broadcast failed. Set when `status` is `failed`, and `null` the rest of the time.

  - `empty_audience`: There was nobody to send to. Either the audience has no members, or every address in it is suppressed.
  - `audience_unavailable`: The audience no longer exists, so there was nothing to resolve.
  - `content_invalid`: The broadcast could not be set up to send. `failure_detail` says exactly what was wrong. It is one of these:
    - The broadcast has no template, or its template has been deleted.
    - The template has no published version, or no sendable content.
    - The template uses a loop that a broadcast cannot fill.
    - The template requires every send to name a language.
    - The sending domain is no longer verified.
    - The IP pool has nothing to send from.
    - The message could not be handed off for delivery.
  - `insufficient_funds`: There was not enough in the workspace balance to pay for the send.
  - `quota_exceeded`: The send would have gone past your organization's daily or monthly email allowance, whichever runs out first. This can happen when the broadcast is being prepared, or partway through sending if the remaining recipients no longer fit. `failure_detail` gives you the count and the limit.
  - `internal_error`: Something went wrong on our side. Retry, and open a support ticket if it keeps happening.

  Possible values: `empty_audience`, `audience_unavailable`, `content_invalid`, `insufficient_funds`, `quota_exceeded`, `internal_error`, `null`
- `data.failure_detail` (nullable string): A sentence explaining the failure in more detail than `failure_reason` does, and `null` when the broadcast has not failed. Show it to the person using your app. Do not write code that reads it, because the wording can change. Branch on `failure_reason` instead.
- `data.recipient_count` (integer, required): Number of recipients after suppressed addresses are removed from the audience. This is 0 until sending starts and the audience becomes a recipient list.
- `data.sent_count` (integer): How many recipients the broadcast has been sent to, counting every recipient whose status is `processed` or later. The number rises while the broadcast is `sending` and stops changing once the broadcast has finished. These counters are exact. The email stats endpoints report on the same sending but are approximate, so use these numbers when you need the precise count. Absent when the broadcast comes back from creating, updating, sending or canceling it, none of which read the counters. Absent from a list row for a broadcast that has no delivery events yet, such as a draft, where reading that one broadcast answers 0 instead. Absent too when the event store cannot be reached, which still returns 200. Read the broadcast again for the numbers.
- `data.delivered_count` (integer): How many recipients' messages were accepted by their mail server. Absent when `sent_count` is.
- `data.bounced_count` (integer): How many recipients the message could not be delivered to at all. Absent when `sent_count` is.
- `data.complained_count` (integer): How many recipients marked the message as spam. Absent when `sent_count` is.
- `data.open_count` (integer): How many times the message was opened, added up across every recipient. One recipient opening it twice counts twice. Absent when `sent_count` is.
- `data.click_count` (integer): How many times a link in the message was clicked, added up across every recipient. One recipient clicking twice counts twice. Absent when `sent_count` is.
- `data.sending_ips` (array of string): The IP addresses this broadcast's messages went out from, up to 100 of them. A broadcast is spread across every address in its pool, so more than one can appear. The receiving mail systems name the address when they deliver, bounce or defer a message, so this stays absent until the first of those comes back. Returned on a single broadcast read, and absent from the list and from the broadcast that creating, updating, sending or canceling one returns, none of which read them. For delivery and latency broken down per address, read the sending-IP stats.
- `data.unique_opens_non_prefetched` (integer): How many distinct recipients opened the message at least once, excluding opens auto-fetched by inbox privacy features (such as Apple Mail Privacy Protection and the Gmail image proxy). A recipient who opened several times, or whose inbox prefetched the message, counts once. Absent when `sent_count` is.
- `data.unique_clicks` (integer): How many distinct recipients clicked a link in the message at least once. A recipient who clicked several times counts once. Absent when `sent_count` is.
- `data.out_of_band_bounces` (integer): How many recipients bounced after the message had already been accepted for delivery. A recipient who bounced this way more than once counts once. Absent when `sent_count` is.
- `data.delivered_recipients` (integer): How many distinct recipients a delivery landed for. This is the denominator to measure `unique_opens_non_prefetched`, `unique_clicks` and `complained_count` against. It differs from `delivered_count`, which reports how many recipients are currently in the delivered state: a recipient who was delivered to and then complained moves to `complained_count` and leaves `delivered_count`, but stays here, because the message did reach them. Absent when `sent_count` is.
- `data.tags` (array of object): Labels on this broadcast, each one a `name` and a `value`, that you can filter and search broadcasts by. Use tags for anything you want to find broadcasts by later, and `metadata` for data you only want handed back to you.
- `data.tags.name` (string, required): Tag name. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 32 characters.
- `data.tags.value` (string, required): Tag value. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 64 characters.
- `data.metadata` (object): Any JSON you want to keep on the broadcast. We store it and hand it back in webhook payloads, and that is all it does. If you want to search or filter by it, use `tags` instead.
- `data.track_opens` (boolean, required): Whether opens are tracked for this broadcast.
- `data.track_clicks` (boolean, required): Whether link clicks are tracked for this broadcast.
- `data.created_at` (string, required): When the broadcast was created.
- `data.scheduled_at` (string): When the broadcast is due to send, and absent when it is not scheduled.
- `data.started_at` (string): When the broadcast started sending. Absent until then. Compare with `sent_at`, which is when the broadcast finished sending.
- `data.sent_at` (nullable string, required): When the last recipient was sent to and the broadcast became `sent`. Null until then. Compare with `started_at`, which is when the broadcast started sending.
- `data.canceled_at` (string): When the broadcast was canceled, and absent if it never was. This is when cancellation was requested, so it is set as soon as the status is `canceling` and does not move while the remaining sends stop and the status becomes `canceled`.
- `next_cursor` (nullable string, required): Cursor for the next page. Pass back as `starting_after` to advance forward. `null` when no next page exists.
- `prev_cursor` (nullable string, required): Cursor for the previous page. Pass back as `ending_before` to step backward. `null` when no previous page exists.
- `refresh_cursor` (nullable string, required): Refresh anchor, the first row of this response. Pass back as `ending_before` to fetch what precedes it in the current sort order. On a newest-first sort those are the items that have appeared since; on any other sort they are the items that sort earlier, so refreshing such a list means re-fetching it instead. Non-`null` whenever `data` is non-empty; `null` only on an empty page. Distinct from `prev_cursor`.

## Related resources

- [Getting started with email](/learn/email/getting-started-with-email) (video)
- [Email](/products/email) (product)
- [Build your first integration](/learn/paths/integration) (course)
- [Send your first email](/docs/get-started/send-your-first-email) (docs)

[Get an implementation brief](/learn/workspace?topic=email)
