List broadcasts
GET
/v1/email/broadcasts
for await (const broadcast of bird.broadcasts.list({ status: ["sent"] })) {
console.log(broadcast.id, broadcast.status);
}for broadcast in client.broadcasts.list(status=["sent"]):
print(broadcast.id, broadcast.status)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)
}foreach ($bird->broadcasts->list(['status' => ['sent']]) as $broadcast) {
echo $broadcast->getId(), ' ', $broadcast->getStatus(), PHP_EOL;
}bird email broadcasts listcurl -X GET "https://us1.platform.bird.com/v1/email/broadcasts" \
-H "Authorization: Bearer $TOKEN" \
--url-query "limit=25"Response200
{
"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"
}
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.
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 Payload
data
array of object
required
Page of broadcast objects.
Show child attributes
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.
Show child attributes
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. 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.
Show child attributes
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.
Show child attributes
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.
Show child attributes
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.
Show child attributes
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
Continue with the documentation, guides and examples for this topic. Resources are in English.
Watch the guideGetting started with emailExplore the capabilityEmailFollow the learning pathBuild your first integrationImplementation guideSend your first email
Try the practice and get an implementation brief