openapi: 3.2.0 info: title: Bird Email Broadcasts API version: 1.0.0 description: 'The Bird API: one REST API for email, SMS, WhatsApp, verification, and Realtime.' servers: - url: https://{region}.platform.bird.com description: 'Regional API endpoint. Use the host for the region your organization is hosted in. Official Bird SDKs and the CLI select it automatically from your API key, so you rarely need to set it by hand. ' variables: region: default: us1 enum: - us1 - eu1 description: The region your organization's data is hosted in. - url: https://platform.bird.com description: Region-independent endpoint for authentication and account administration. - url: http://localhost:8080 description: Local development. security: - BearerAuth: [] tags: - name: email-broadcasts description: Send one email to a stored audience. Create a broadcast as a draft, then send it immediately or schedule it for later; scheduled and in-progress broadcasts can be canceled. The audience's contacts at send time become the recipient set after suppressions are applied. The recipients endpoint returns each recipient's delivery state. paths: /v1/email/broadcasts: post: operationId: createEmailBroadcast summary: Create a broadcast description: 'Creates a broadcast for a stored audience. The default is an editable draft. Set `send` to `true` to send now or at `scheduled_at`; `scheduled_at` without `send` set to `true` returns `422`. Use Create an email message for a small, explicit recipient list. Contacts in the audience at send time become recipients after suppressions. Read their states with List recipients of a broadcast. With `send`, the request returns `422` if any of these are true: - The sender domain is not verified. - No audience is selected, or the selected audience no longer exists. - The audience has no contact with an email address, or every contact is suppressed for the broadcast''s category. - No template is set, or the set template has no published version. - The organization has used its broadcast allowance for the current billing period, or (for an immediate send) is already at its concurrent-broadcast limit; schedule the send instead to wait for a free slot. If the audience is empty when a scheduled send starts, the broadcast becomes `failed` with a readable reason instead of silently dropping it.' tags: - email-broadcasts x-audiences: - public - command x-snippet-key: broadcasts.create security: - BearerAuth: [] - CookieAuth: [] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EmailBroadcastCreateRequest' responses: '201': description: Draft broadcast created. headers: Idempotency-Replay: $ref: '#/components/headers/IdempotencyReplay' content: application/json: schema: $ref: '#/components/schemas/EmailBroadcast' example: id: eb_01krdgeqcxet5s7t44vh8rt9mg from: email: jane@acme.com name: Jane Doe audience_id: adn_01krdgeqcxet5s7t44vh8rt9mg template: id: emt_01krdgeqcxet5s7t44vh8rt9mg version_id: null category: marketing reply_to: - email: jane@acme.com name: Jane Doe tags: - name: category value: welcome track_opens: true track_clicks: true created_at: '2026-09-01T09:14:02.418Z' recipient_count: 0 sent_at: null status: draft '202': description: Broadcast accepted for delivery. headers: Idempotency-Replay: $ref: '#/components/headers/IdempotencyReplay' content: application/json: schema: $ref: '#/components/schemas/EmailBroadcast' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/Unprocessable' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk get: operationId: listEmailBroadcasts summary: List broadcasts description: 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. tags: - email-broadcasts x-audiences: - public - command x-snippet-key: broadcasts.list security: - BearerAuth: [] - CookieAuth: [] parameters: - $ref: '#/components/parameters/PaginationLimit' - $ref: '#/components/parameters/StartingAfter' - $ref: '#/components/parameters/EndingBefore' - $ref: '#/components/parameters/EmailBroadcastStatusFilter' - $ref: '#/components/parameters/EmailBroadcastAudienceFilter' - $ref: '#/components/parameters/EmailBroadcastTagFilter' - $ref: '#/components/parameters/EmailBroadcastSearchFilter' - $ref: '#/components/parameters/CreatedAfter' - $ref: '#/components/parameters/CreatedBefore' responses: '200': description: Paginated list of broadcasts. content: application/json: schema: $ref: '#/components/schemas/EmailBroadcastList' example: 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 '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/Unprocessable' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' x-surfaces: - cli - make - mcp - sdk /v1/email/broadcasts/export: get: operationId: getEmailBroadcastsExport summary: Export broadcasts as CSV description: 'Downloads the workspace''s broadcasts as a CSV file, one row per broadcast, newest first. The header names fifteen columns, in this order: `created_at`, `broadcast_id`, `status`, `template_name`, `audience_id`, `audience_name`, `recipient_count`, `sent_count`, `delivered_count`, `track_opens`, `open_rate`, `track_clicks`, `click_rate`, `scheduled_at` and `sent_at`. `broadcast_id` is what joins a row back to the rest of the API. It takes the same filters as the broadcast list, so the file matches what the list shows for the same query. `recipient_count` is empty until a send resolves the audience, so an empty cell there means the broadcast has no recipient list yet rather than a list of nobody. `open_rate` and `click_rate` are decimal fractions to four places, over the recipients the message reached: `open_rate` is unique non-prefetched opens divided by that number, and `click_rate` is unique clicks divided by it. That divisor is not the `delivered_count` column, which resolves each recipient to a single status and so counts a recipient who delivered and then complained under the complaint, so neither rate can be reproduced from this file alone. Either is empty when its `track_opens` or `track_clicks` flag is `false`, and also when the message reached nobody, so an empty rate does not mean tracking is off. Both flags default to `true` and the export narrows by no status of its own, so a `draft` or `scheduled` row is included unless `status` excludes it, and carries `track_opens` as `true` with `open_rate` empty. A text cell whose first character is `=`, `+`, `-`, `@`, a tab or a carriage return is written with a leading apostrophe, so a spreadsheet reads it as text rather than as a formula. A value that begins with an apostrophe of its own is written unchanged, so the original text cannot always be recovered from the CSV cell. Match records by `broadcast_id` or `audience_id`, rather than by `template_name` or `audience_name`. The file is complete or it is refused: a query matching more broadcasts than one file carries returns a 422 naming the limit rather than a truncated download. Narrow it with `created_after` and `created_before` and request each window separately.' tags: - email-broadcasts x-audiences: - public x-snippet-key: none security: - BearerAuth: [] - CookieAuth: [] parameters: - $ref: '#/components/parameters/EmailBroadcastStatusFilter' - $ref: '#/components/parameters/EmailBroadcastAudienceFilter' - $ref: '#/components/parameters/EmailBroadcastTagFilter' - $ref: '#/components/parameters/EmailBroadcastSearchFilter' - $ref: '#/components/parameters/CreatedAfter' - $ref: '#/components/parameters/CreatedBefore' responses: '200': description: The broadcasts as a CSV file. headers: Content-Disposition: $ref: '#/components/headers/ContentDisposition' content: text/csv: schema: type: string format: binary '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/Unprocessable' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' /v1/email/broadcasts/{broadcast_id}: get: operationId: getEmailBroadcast summary: Get a broadcast description: 'Returns one broadcast, with its audience reference and counters. The full recipient list, with each recipient''s own delivery status, is paginated separately: see List recipients of a broadcast.' tags: - email-broadcasts x-audiences: - public - command x-snippet-key: broadcasts.get security: - BearerAuth: [] - CookieAuth: [] parameters: - name: broadcast_id in: path required: true description: Broadcast identifier. Starts with `eb_`. schema: type: string pattern: ^eb_[0-9a-hjkmnp-tv-z]{26}$ responses: '200': description: The broadcast. content: application/json: schema: $ref: '#/components/schemas/EmailBroadcast' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/Unprocessable' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' x-surfaces: - cli - make - mcp - sdk patch: operationId: updateEmailBroadcast summary: Update a broadcast description: 'Updates a draft or scheduled broadcast. A field you supply in the request is changed. A field you leave out is left as it is. A scheduled broadcast stays editable right up until it is accepted for sending. After that, editing it returns a conflict error.' tags: - email-broadcasts x-audiences: - public - command x-snippet-key: broadcasts.update security: - BearerAuth: [] - CookieAuth: [] parameters: - name: broadcast_id in: path required: true description: Broadcast identifier. Starts with `eb_`. schema: type: string pattern: ^eb_[0-9a-hjkmnp-tv-z]{26}$ - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EmailBroadcastUpdateRequest' responses: '200': description: The updated broadcast. content: application/json: schema: $ref: '#/components/schemas/EmailBroadcast' example: id: eb_01krdgeqcxet5s7t44vh8rt9mg from: email: jane@acme.com name: Jane Doe audience_id: adn_01krdgeqcxet5s7t44vh8rt9mg template: id: emt_01krdgeqcxet5s7t44vh8rt9mg version_id: null category: marketing reply_to: - email: jane@acme.com name: Jane Doe tags: - name: category value: welcome track_opens: true track_clicks: true created_at: '2026-09-01T09:14:02.418Z' status: scheduled recipient_count: 0 scheduled_at: '2026-09-02T08:00:00Z' sent_at: null '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/Unprocessable' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk delete: operationId: deleteEmailBroadcast summary: Delete a broadcast description: Deletes a draft broadcast. Only a draft can be deleted. To stop a scheduled or sending broadcast, use Cancel a broadcast instead. tags: - email-broadcasts x-audiences: - public - command x-snippet-key: broadcasts.delete security: - BearerAuth: [] - CookieAuth: [] parameters: - name: broadcast_id in: path required: true description: Broadcast identifier. Starts with `eb_`. schema: type: string pattern: ^eb_[0-9a-hjkmnp-tv-z]{26}$ - $ref: '#/components/parameters/IdempotencyKey' responses: '204': description: Draft broadcast deleted. '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/Unprocessable' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk /v1/email/broadcasts/{broadcast_id}/counts: get: operationId: getEmailBroadcastCounts summary: Get a broadcast's audience counts description: 'Returns a live estimate of how many contacts the broadcast can reach, narrowing from everyone in its audience down to the ones the send would go to. - `total` is every contact in the audience. - `addressable` is how many of those have an email address. - `sendable` is how many addressable contacts are not suppressed for the broadcast''s category. A transactional broadcast still reaches a contact who unsubscribed from or complained about marketing mail. A marketing broadcast does not. The estimate can change until the broadcast sends. The broadcast must have an audience selected; otherwise, the request returns `422`. These are audience numbers, and sending does not change them: after a send they still describe who the broadcast resolved to, not what happened to them. `status` says which of the two situations you are in. For delivery outcomes, read the broadcast''s recipients for per-recipient status, its events for the raw feed, or email statistics by broadcast for the aggregates.' tags: - email-broadcasts x-audiences: - public - command x-snippet-key: broadcasts.counts security: - BearerAuth: [] - CookieAuth: [] parameters: - name: broadcast_id in: path required: true description: Broadcast identifier. Starts with `eb_`. schema: $ref: '#/components/schemas/EmailBroadcastID' responses: '200': description: The broadcast's audience counts. content: application/json: schema: $ref: '#/components/schemas/EmailBroadcastCounts' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/Unprocessable' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' x-surfaces: - cli - make - mcp - sdk /v1/email/broadcasts/{broadcast_id}/send-quota: get: operationId: getEmailBroadcastSendQuota summary: Get how much of a broadcast the send allowance covers description: 'Returns how much of the broadcast the organization''s email send allowance covers, so a send that would run past it can be reconsidered rather than failing part-way. `recipients` is how many contacts the broadcast would send to right now, the same number the audience counts report as sendable. `allowed` is how many of those the allowance covers, and equals `recipients` when nothing limits the send. `limited_by` names the allowance that stops the rest, either the monthly one that runs with the billing period or the daily one, with `limit` and `remaining` describing it. Both numbers are live estimates: audience membership, suppressions and the sends already made in the current window all move until the broadcast sends. The broadcast must have an audience selected; a broadcast with no audience yet returns a 422.' tags: - email-broadcasts x-audiences: - public - command x-snippet-key: broadcasts.send_quota security: - BearerAuth: [] - CookieAuth: [] parameters: - name: broadcast_id in: path required: true description: ID of the broadcast whose send allowance to check. schema: $ref: '#/components/schemas/EmailBroadcastID' responses: '200': description: How much of the broadcast the send allowance covers. content: application/json: schema: $ref: '#/components/schemas/EmailBroadcastSendQuota' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/Unprocessable' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' x-surfaces: - cli - make - mcp - sdk /v1/email/broadcasts/{broadcast_id}/recipients: get: operationId: listEmailBroadcastRecipients summary: List recipients of a broadcast description: 'Returns recipient-level delivery state for a broadcast, paginated. This is who a broadcast reached and how far each one got. The list is empty for a draft, scheduled, or accepted broadcast: recipients are resolved from the audience only once sending begins. Pass `to` to return just that address''s row, which is how you answer "did this person get it?" on a send too large to page through.' tags: - email-broadcasts x-audiences: - public - command x-snippet-key: broadcasts.list_recipients security: - BearerAuth: [] - CookieAuth: [] parameters: - name: broadcast_id in: path required: true description: ID of the broadcast whose recipients to read. schema: type: string pattern: ^eb_[0-9a-hjkmnp-tv-z]{26}$ - $ref: '#/components/parameters/PaginationLimit' - $ref: '#/components/parameters/StartingAfter' - $ref: '#/components/parameters/EndingBefore' - name: to in: query required: false description: Return only the recipient at this address. Exact match, normalised to lowercase before comparison, so it returns at most one row. schema: type: string format: email example: user@example.com responses: '200': description: Paginated list of recipients for this broadcast. content: application/json: schema: $ref: '#/components/schemas/EmailRecipientList' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/Unprocessable' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' x-surfaces: - cli - make - mcp - sdk /v1/email/broadcasts/{broadcast_id}/recipients/export: get: operationId: getEmailBroadcastRecipientsExport summary: Export a broadcast's recipients as CSV description: 'Downloads a broadcast''s recipients as a CSV file, one row per recipient. The header names ten columns, in this order: `recipient`, `status`, `rejection_reason`, `open_count`, `click_count`, `processed_at`, `delivered_at`, `processing_latency_ms`, `delivery_latency_ms` and `total_latency_ms`. `rejection_reason` is filled only on a `status: rejected` row and names why the message was never sent, which is not why it bounced. A bounce happens after the send, so no column here carries `bounce_type` or `bounce_code`: read List recipients of a broadcast for those. The three latency columns are milliseconds, and each is empty until the milestone it measures has happened: `processing_latency_ms` from the send being accepted to the message being prepared for delivery, `delivery_latency_ms` from prepared to the receiving mail server accepting it, and `total_latency_ms` the accepted-to-delivered time end to end. A text cell whose first character is `=`, `+`, `-`, `@`, a tab or a carriage return is written with a leading apostrophe, so a spreadsheet reads it as text rather than as a formula. Nothing else is prefixed, so a value that begins with an apostrophe of its own is written unchanged: strip a leading apostrophe only when one of those six characters follows it, before matching `recipient` back to the API. The file is complete or it is refused: a broadcast with more recipients than one file carries returns a 422 naming the limit rather than a truncated download. Pass `to` to export one address, or page List recipients of a broadcast, which has no limit.' tags: - email-broadcasts x-audiences: - public x-snippet-key: none security: - BearerAuth: [] - CookieAuth: [] parameters: - name: broadcast_id in: path required: true description: Broadcast identifier. Starts with `eb_`. schema: type: string pattern: ^eb_[0-9a-hjkmnp-tv-z]{26}$ - name: to in: query required: false description: Return only the recipient at this address. Exact match, normalised to lowercase before comparison, so the file carries at most one row. schema: type: string format: email example: user@example.com responses: '200': description: The broadcast's recipients as a CSV file. headers: Content-Disposition: $ref: '#/components/headers/ContentDisposition' content: text/csv: schema: type: string format: binary '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/Unprocessable' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' /v1/email/broadcasts/{broadcast_id}/events: get: operationId: listEmailBroadcastEvents summary: List events for a broadcast description: Returns the per-recipient delivery timeline for a broadcast, oldest first, as a cursor page. Each entry is one event, such as a send, an open, a click, or a bounce. tags: - email-broadcasts x-audiences: - public - command x-snippet-key: broadcasts.list_events security: - BearerAuth: [] - CookieAuth: [] parameters: - name: broadcast_id in: path required: true description: ID of the broadcast whose event timeline to read. schema: type: string pattern: ^eb_[0-9a-hjkmnp-tv-z]{26}$ - $ref: '#/components/parameters/PaginationLimit' - $ref: '#/components/parameters/StartingAfter' - $ref: '#/components/parameters/EndingBefore' - name: type in: query required: false description: Filter by event type, for example `email.bounced` or `email.opened`. A broadcast timeline is recipient-scoped, so `email.scheduled` and `email.canceled` never appear on it; a canceled broadcast reports that in its own `status`. schema: $ref: '#/components/schemas/EmailEventType' responses: '200': description: Paginated event timeline for this broadcast. content: application/json: schema: $ref: '#/components/schemas/EmailEventList' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/Unprocessable' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' x-surfaces: - cli - make - mcp - sdk /v1/email/broadcasts/{broadcast_id}/clicked-links: get: operationId: listEmailBroadcastClickedLinks summary: List a broadcast's clicked links description: 'Returns the destination URLs a broadcast''s recipients clicked, grouped by URL and sorted by click count. Each entry carries an exact click count and distinct-recipient count over every click event the broadcast has, plus the link''s name: the name used by the most clicks that named it, or null if no click through that URL ever carried one. `data` is capped at the 100 most-clicked URLs; `total` carries the actual number of distinct URLs clicked, so a capped response is never mistaken for a complete one.' tags: - email-broadcasts x-audiences: - public - command x-snippet-key: broadcasts.list_clicked_links security: - BearerAuth: [] - CookieAuth: [] parameters: - name: broadcast_id in: path required: true description: ID of the broadcast whose clicked links to read. schema: $ref: '#/components/schemas/EmailBroadcastID' responses: '200': description: The broadcast's clicked links, grouped by URL. content: application/json: schema: $ref: '#/components/schemas/EmailBroadcastClickedLinkList' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/Unprocessable' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' x-surfaces: - cli - make - mcp - sdk /v1/email/broadcasts/{broadcast_id}/send: post: operationId: sendEmailBroadcast summary: Send a broadcast description: 'Sends a draft or scheduled broadcast, either immediately or at `scheduled_at`. Calling this on a scheduled broadcast replaces its schedule or sends it now if `scheduled_at` is omitted. Calling it again on a broadcast that is already `accepted` retries dispatch rather than returning an error. A broadcast that has started sending, or that already reached a final state, returns a conflict. The audience''s contacts at send time become the recipients after suppressions. The request returns `422` if any of these are true: - The sender domain is not verified. - No audience is selected, or the selected audience no longer exists. - The audience has no contact with an email address, or every contact is suppressed for the broadcast''s category. - No template is set, or the set template has no published version. - The organization has used its broadcast allowance for the current billing period, or (for an immediate send) is already at its concurrent-broadcast limit; schedule the send instead to wait for a free slot. If the audience is empty when a scheduled send starts, the broadcast becomes `failed` with a readable reason instead of silently dropping it.' tags: - email-broadcasts x-audiences: - public - command x-snippet-key: broadcasts.send security: - BearerAuth: [] - CookieAuth: [] parameters: - name: broadcast_id in: path required: true description: Broadcast identifier. Starts with `eb_`. schema: type: string pattern: ^eb_[0-9a-hjkmnp-tv-z]{26}$ - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/EmailBroadcastSendNowRequest' responses: '202': description: Broadcast accepted for delivery. headers: Idempotency-Replay: $ref: '#/components/headers/IdempotencyReplay' content: application/json: schema: $ref: '#/components/schemas/EmailBroadcast' example: id: eb_01krdgeqcxet5s7t44vh8rt9mg from: email: jane@acme.com name: Jane Doe audience_id: adn_01krdgeqcxet5s7t44vh8rt9mg template: id: emt_01krdgeqcxet5s7t44vh8rt9mg version_id: null category: marketing reply_to: - email: jane@acme.com name: Jane Doe tags: - name: category value: welcome track_opens: true track_clicks: true created_at: '2026-09-01T09:14:02.418Z' status: accepted recipient_count: 0 sent_at: null '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/Unprocessable' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk /v1/email/broadcasts/{broadcast_id}/cancel: post: operationId: cancelEmailBroadcast summary: Cancel a broadcast description: 'Cancels a scheduled, accepted, or sending broadcast. Canceling it while it is sending stops every delivery that has not gone out yet, though messages already on their way to a recipient are not recalled. Calling this again on a broadcast that is already canceling or canceled is idempotent: it returns the broadcast''s current state rather than an error. A draft cannot be canceled, because it was never sent; use Delete a broadcast instead. A broadcast that already sent or failed has reached a terminal state and returns a conflict.' tags: - email-broadcasts x-audiences: - public - command x-snippet-key: broadcasts.cancel security: - BearerAuth: [] - CookieAuth: [] parameters: - name: broadcast_id in: path required: true description: Broadcast identifier. Starts with `eb_`. schema: type: string pattern: ^eb_[0-9a-hjkmnp-tv-z]{26}$ - $ref: '#/components/parameters/IdempotencyKey' responses: '202': description: Cancellation accepted. headers: Idempotency-Replay: $ref: '#/components/headers/IdempotencyReplay' content: application/json: schema: $ref: '#/components/schemas/EmailBroadcast' example: 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 tags: - name: category value: welcome track_opens: true track_clicks: true created_at: '2026-09-01T09:14:02.418Z' status: canceling failure_reason: null failure_detail: null recipient_count: 4820 scheduled_at: '2026-09-02T08:00:00Z' started_at: '2026-09-02T08:00:03.771Z' sent_at: null canceled_at: '2026-09-02T08:06:12.204Z' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/Unprocessable' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk components: parameters: CreatedBefore: name: created_before in: query required: false description: 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. schema: type: string format: date-time example: '2026-06-01T00:00:00Z' CreatedAfter: name: created_after in: query required: false description: 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. schema: type: string format: date-time example: '2026-05-01T00:00:00Z' IdempotencyKey: name: Idempotency-Key in: header required: false description: "Client-supplied key. On operations supporting request deduplication, a retained\nresponse is replayed for duplicate requests with the same key within the\nidempotency window (3 hours by default). This protection requires a workspace,\norganization, or staff-account scope. User-only and unauthenticated operations,\nstreams, and operations with a separate replay contract do not use this\nresponse replay.\n\nOn a supported operation, if idempotency protection is unavailable before execution, the API returns\n`503 IdempotencyUnavailable` (E01033) without executing this attempt. Retry with\nbackoff using the same key and request. An operation that takes effect before\nits response is retained can still execute again on retry.\n\nTwo distinct 409 errors signal misuse:\n\n- `request_in_progress` (E01004): The same key is currently being\n processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds.\n- `idempotency_key_reuse` (E01005): The same key has already completed\n against a different request body or method. Generate a new key.\n\nRecommended key format is `/` (for example `welcome-user/usr_abc123`).\n" schema: type: string maxLength: 255 EmailBroadcastSearchFilter: name: q in: query required: false description: 'Case-insensitive substring match against the broadcast''s tag names and values, or the referenced template''s name. ' schema: type: string maxLength: 200 EmailBroadcastStatusFilter: name: status in: query required: false description: 'Filter by lifecycle status. Repeat the parameter to match more than one status, for example `?status=accepted&status=sending`. ' style: form explode: true schema: type: array items: $ref: '#/components/schemas/EmailBroadcastStatus' EmailBroadcastTagFilter: name: tag in: query required: false description: '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`. ' schema: type: string StartingAfter: name: starting_after in: query required: false description: Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. schema: type: string EmailBroadcastAudienceFilter: name: audience_id in: query required: false description: Filter by audience. Only broadcasts that use this audience are returned. schema: $ref: '#/components/schemas/AudienceID' EndingBefore: name: ending_before in: query required: false description: 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. schema: type: string PaginationLimit: name: limit in: query required: false description: Maximum number of items to return per page. schema: type: integer minimum: 1 maximum: 100 default: 25 schemas: AudienceID: type: string minLength: 1 pattern: ^adn_[0-9a-hjkmnp-tv-z]{26}$ example: adn_01krdgeqcxet5s7t44vh8rt9mg ErrorBody: type: object additionalProperties: false required: - type - code - name - message - doc_url - request_id properties: type: type: string minLength: 1 description: Broad category for coarse client branching. enum: - auth_error - bad_request_error - billing_error - conflict_error - gone_error - internal_error - misdirected_error - not_found_error - not_implemented_error - payload_too_large_error - permission_error - precondition_error - rate_limit_error - service_unavailable_error - too_early_error - validation_error code: type: string minLength: 1 pattern: ^E\d{5}$ description: Opaque, stable, unique error identifier. Never reused. name: type: string minLength: 1 description: Human-readable slug for log readability. Paired with code, never replaces it. message: type: string minLength: 1 description: Human-readable description. Not stable; clients must not parse it. param: type: string minLength: 1 description: Identifies the offending field. Omitted when not applicable. doc_url: type: string minLength: 1 format: uri description: Stable link to the docs page for this error code. request_id: type: string minLength: 1 description: Request correlation ID for support and troubleshooting. Also returned in the `X-Request-Id` response header. vendor_code: type: string minLength: 1 description: 'Verbatim error code from an external system, such as an SMTP response code or a payment decline code. Present only when the code may help you resolve the error. ' details: type: array description: Per-field validation errors. Present only on validation_error responses. items: $ref: '#/components/schemas/ErrorDetail' remediation: type: string minLength: 1 description: A human-readable next step to resolve this error. Present when a recovery is known. next: type: array description: 'The steps that resolve this error. Perform them in order, re-reading after each; a `wait` or `terminal` step is always last. Present for errors with a well-defined recovery, such as unmet preconditions and conflicts. ' items: $ref: '#/components/schemas/NextAction' EmailBroadcastCounts: type: object additionalProperties: false required: - broadcast_id - status - total - addressable - sendable description: 'How many people a broadcast would reach right now, narrowing from everyone in the audience down to the ones it could actually be sent to. These are live numbers, worked out at the moment you ask. Audience membership and suppressions change, so they can drift between now and when the broadcast sends. **They are about the audience, not about delivery, and sending does not change them.** Once the broadcast has sent, its own `recipient_count` is the number that actually went out, and what each of those recipients did with the message is in [the broadcast''s recipients](/docs/api/reference/list-email-broadcast-recipients) and [its events](/docs/api/reference/list-email-broadcast-events). `status` is here so you can tell which question these numbers are answering, and `broadcast_id` names what they are about. ' properties: broadcast_id: readOnly: true description: The broadcast these counts are for. allOf: - $ref: '#/components/schemas/EmailBroadcastID' status: readOnly: true example: draft description: 'Where the broadcast is in its lifecycle, so the counts read in context. Anything past `draft` or `scheduled` means these numbers describe an audience the broadcast has already been sent to, not one it is about to reach. ' allOf: - $ref: '#/components/schemas/EmailBroadcastStatus' next: type: array readOnly: true description: 'What to do next, given where the broadcast is. On a broadcast that has already sent this names the reads that carry delivery outcomes, which these counts never do. An empty list means there is nothing to do; the field is absent entirely on responses that do not report next actions. ' items: $ref: '#/components/schemas/NextAction' total: type: integer format: int64 minimum: 0 readOnly: true example: 13000 description: How many contacts are in the audience. addressable: type: integer format: int64 minimum: 0 readOnly: true example: 12500 description: How many of those contacts have an email address. A contact with no address is not counted. This is never higher than `total`. sendable: type: integer format: int64 minimum: 0 readOnly: true example: 12000 description: 'How many of the addressable contacts are not suppressed for this broadcast''s category, which is who the email would actually go to. This is never higher than `addressable`. Which suppressions apply depends on the category, so the same audience can give a higher number for a transactional broadcast than for a marketing one. A transactional broadcast still reaches people who unsubscribed from or complained about marketing mail, and a marketing broadcast does not. ' EmailSendAllowanceWindow: type: string minLength: 1 enum: - none - monthly - daily description: 'Which of the organization''s email send allowances stops a send from reaching its whole audience. - `none`: every recipient is covered. - `monthly`: the allowance that runs with the billing period. - `daily`: the allowance that resets at the end of each UTC day. When both apply, the tighter of the two is reported. ' RecipientID: type: string minLength: 1 pattern: ^er_[0-9a-hjkmnp-tv-z]{26}$ example: er_01krdgeqcxet5s7t44vh8rt9mg EmailBroadcastClickedLink: type: object additionalProperties: false required: - url - name - click_count - recipient_count description: 'One destination URL a broadcast''s recipients clicked, with its exact click and recipient totals. Grouped over every click event the broadcast has, not a sample. ' properties: url: type: string minLength: 1 readOnly: true example: https://acme.com/whats-new/faster-exports description: The clicked URL. name: type: - string - 'null' readOnly: true description: 'What the link said, resolved by the name used by the most clicks that carried one. Null when no click through this URL ever carried a name. ' example: Faster exports, docs click_count: type: integer format: int64 minimum: 0 readOnly: true example: 431 description: Total clicks through this URL, including clicks that carried no link name. recipient_count: type: integer format: int64 minimum: 0 readOnly: true example: 388 description: Number of distinct recipients who clicked this URL at least once. EmailEventList: allOf: - type: object required: - data properties: data: type: array description: Page of timeline events for this email send, in chronological order. items: $ref: '#/components/schemas/EmailEvent' next: type: array readOnly: true description: 'What to do next, given what this page reports. Present only where the read computes it: an empty list means the answer you were looking for is here and there is nothing further to do. Absent entirely on reads that do not report next actions. ' items: $ref: '#/components/schemas/NextAction' - $ref: '#/components/schemas/_ListEnvelope' EmailBroadcastID: type: string minLength: 1 pattern: ^eb_[0-9a-hjkmnp-tv-z]{26}$ example: eb_01krdgeqcxet5s7t44vh8rt9mg EmailBroadcastTemplate: type: object additionalProperties: false required: - id description: 'The template a broadcast sends, and the exact version of it the broadcast is fixed to. The template cannot be one that requires every send to name a language, because a broadcast never names one, so a template that insists on it has nothing to work with. ' properties: id: $ref: '#/components/schemas/EmailTemplateID' description: 'Which template the broadcast sends. Which version of it the send is fixed to is `version_id`. ' version_id: oneOf: - $ref: '#/components/schemas/EmailTemplateVersionID' - type: 'null' readOnly: true description: '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. ' EmailTemplateID: type: string minLength: 1 pattern: ^emt_[0-9a-hjkmnp-tv-z]{26}$ example: emt_01krdgeqcxet5s7t44vh8rt9mg EmailBroadcastSendNowRequest: type: object additionalProperties: false properties: scheduled_at: type: string format: date-time description: When to send the broadcast. It has to be at least 30 seconds and at most 365 days from now. Leave it out to send straight away. example: scheduled_at: '2026-12-01T09:00:00Z' EmailEventType: type: string minLength: 1 description: 'Type of an event in a message''s per-recipient delivery timeline. - `email.scheduled`: We accepted a send scheduled for a future time. Fires once for each message regardless of its recipient count. - `email.accepted`: We accepted the send and are getting ready to deliver it. Fires once per requested recipient. - `email.processed`: We queued the message for delivery to the recipient''s mail server. - `email.deferred`: The recipient''s mail server temporarily refused the message. Delivery remains pending and is retried. Can fire more than once per recipient. - `email.delivered`: The recipient''s mail server accepted the message. - `email.bounced`: Delivery permanently failed at the recipient''s mail server. - `email.out_of_band_bounce`: A bounce notification arrived after the message had already been accepted for delivery. - `email.rejected`: We rejected the message before attempting delivery, for example because the recipient is suppressed. - `email.canceled`: A scheduled send was canceled before it fired. Fires once for each message regardless of its recipient count. - `email.opened`: The recipient opened the message. Can fire more than once per recipient. - `email.clicked`: The recipient clicked a tracked link in the message. Can fire more than once per recipient. - `email.unsubscribed`: The recipient opted out through a tracked unsubscribe link in the message. - `email.list_unsubscribed`: The recipient opted out through the one-click unsubscribe control in their mail client. - `email.complained`: The recipient reported the message as spam through their mailbox provider. We can add new event types to this list over time, so treat a value you do not recognize as a new type rather than as an error. ' x-extensible-enum: - email.accepted - email.bounced - email.canceled - email.clicked - email.complained - email.deferred - email.delivered - email.list_unsubscribed - email.opened - email.out_of_band_bounce - email.processed - email.rejected - email.scheduled - email.unsubscribed example: email.delivered EmailBroadcastClickedLinkList: type: object additionalProperties: false required: - data - total properties: data: type: array description: The broadcast's clicked URLs, most-clicked first, capped at 100 rows. items: $ref: '#/components/schemas/EmailBroadcastClickedLink' total: type: integer minimum: 0 readOnly: true description: 'Total number of distinct URLs the broadcast''s recipients clicked, regardless of the cap on `data`. When it exceeds the number of rows returned, the list was capped at the 100 most-clicked URLs. ' example: 57 EmailTemplateVersionID: type: string minLength: 1 pattern: ^emv_[0-9a-hjkmnp-tv-z]{26}$ example: emv_01krdgeqcxet5s7t44vh8rt9mg _ListEnvelope: type: object required: - next_cursor - prev_cursor - refresh_cursor properties: next_cursor: type: - string - 'null' description: Cursor for the next page. Pass back as `starting_after` to advance forward. `null` when no next page exists. example: eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE0OjAzOjEwWlwiIiwiaSI6IjAxOTJmM2IxLTRjN2UtN2EyYi05ZDYxLThmM2E1YzJlN2I0MCJ9 prev_cursor: type: - string - 'null' description: Cursor for the previous page. Pass back as `ending_before` to step backward. `null` when no previous page exists. example: null refresh_cursor: type: - string - 'null' description: 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`. example: eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE2OjQyOjAxWlwiIiwiaSI6IjAxOTJmM2IxLTllMDQtN2NkMy1iODE3LTJhNmY0ZDFjOGUwOSJ9 EmailEvent: type: object additionalProperties: false required: - id - type - occurred_at - recipient_id properties: id: type: string minLength: 1 readOnly: true pattern: ^ev_[0-9a-hjkmnp-tv-z]{26}$ description: Event ID. example: ev_01krdgeqcxet5s7t44vh8rt9mg type: $ref: '#/components/schemas/EmailEventType' description: 'The event''s type. `email.processed`, for example, means the message has been processed and queued for delivery. ' occurred_at: type: string format: date-time minLength: 1 description: When this event occurred. recipient_id: $ref: '#/components/schemas/RecipientID' description: Recipient this event applies to. bounce_type: type: - string - 'null' enum: - hard - soft - undetermined - admin - block - null description: 'Bounce classification. Present on `email.bounced`, `email.out_of_band_bounce`, and `email.deferred` events. - `hard`: a permanent failure (invalid address or non-existent domain). - `soft`: a transient failure (mailbox full or server temporarily unavailable). - `block`: the receiving mail server blocked the sending IP for reputation reasons. - `admin`: an administrative refusal (relaying denied or blocklisted domain). - `undetermined`: the receiving server''s response is ambiguous. ' bounce_class: type: - integer - 'null' minimum: 1 maximum: 255 description: 'A more detailed numeric bounce code, useful for telling apart failures that share the same `bounce_type`. For example, a DNS failure and a spam block can both come through as `bounce_type: soft` or `bounce_type: block`; this field tells you which one actually happened. Present on `email.bounced`, `email.out_of_band_bounce`, and `email.deferred` events. ' bounce_code: type: - string - 'null' description: 'SMTP status code returned by the receiving mail server. Present on `email.bounced` and `email.deferred` events. ' example: 5.1.1 bounce_description: type: - string - 'null' description: The bounce reason, in plain language, as reported by the mail server. Present on `email.bounced` and `email.deferred` events. rejection_reason: type: - string - 'null' enum: - recipient_suppressed - transmission_failed - generation_failure - policy_rejection - domain_unverified - quota_exceeded - recipient_not_allowed - null description: 'Specific cause of rejection. Present on `email.rejected` events only. - `recipient_suppressed`: The recipient is on the workspace suppression list. - `transmission_failed`: The message could not be transmitted for delivery. - `generation_failure`: The message could not be built for delivery, because of a template or content issue. - `policy_rejection`: The message was refused by sending policy. - `domain_unverified`: The sending domain was not verified. - `quota_exceeded`: The organization''s send quota was reached. - `recipient_not_allowed`: This recipient was not allowed for this send. For a send from the shared onboarding domain, every recipient has to be a verified member of the workspace. ' sending_ip: type: - string - 'null' description: 'The IP address used to send this message. Useful for spotting a deliverability problem that is tied to one specific sending IP rather than affecting all of them. Present on `email.delivered`, `email.bounced`, `email.out_of_band_bounce`, and `email.deferred` events. ' mailbox_provider: type: - string - 'null' description: 'The recipient mailbox provider, as a lowercased classifier bucket (e.g. `gmail`, `yahoo`, `microsoft`, `apple`). Present on `email.processed`, `email.delivered`, `email.complained`, `email.bounced`, `email.out_of_band_bounce`, `email.deferred`, `email.rejected`, `email.unsubscribed`, `email.list_unsubscribed`, `email.opened`, and `email.clicked` events when the receiving mail system could be classified; null when it could not. ' mailbox_provider_region: type: - string - 'null' description: 'The provider region, as reported by the receiving mail system (for example `NA`, `EU`, `APAC`). The set is open and provider-specific. Present on `email.processed`, `email.delivered`, `email.complained`, `email.bounced`, `email.out_of_band_bounce`, `email.deferred`, `email.rejected`, `email.unsubscribed`, `email.list_unsubscribed`, `email.opened`, and `email.clicked` events when reported; null otherwise. ' is_prefetched: type: - boolean - 'null' description: 'True when the open was auto-fetched by an inbox privacy feature (Apple Mail Privacy Protection, the Gmail image proxy) rather than a person actually opening the message. Use it to calculate open rate accurately. Present on `email.opened` events only. ' url: type: - string - 'null' description: The clicked URL. Present on `email.clicked` events, and on `email.unsubscribed` events when the recipient unsubscribed through a link in the message. link_name: type: - string - 'null' description: The clicked link's own name, when the link in the message carried one, so a click can be reported by what the link said rather than where it pointed. Absent when the link had no name. Appears alongside `url` on `email.clicked` events, and on `email.unsubscribed` events when the recipient unsubscribed through a link. example: Faster exports, docs country: type: - string - 'null' minLength: 2 maxLength: 2 description: ISO 3166-1 alpha-2 country code derived from the client IP. Present on `email.opened` and `email.clicked` events when available. example: US ip_address: type: - string - 'null' description: Client IP address (IPv4 or IPv6). Present on `email.opened` and `email.clicked` events when available. user_agent: type: - string - 'null' description: Client user-agent string. Present on `email.opened` and `email.clicked` events when available. EmailBroadcast: type: object additionalProperties: false required: - id - category - status - recipient_count - track_opens - track_clicks - created_at - sent_at properties: id: type: string minLength: 1 readOnly: true pattern: ^eb_[0-9a-hjkmnp-tv-z]{26}$ description: Broadcast ID. example: eb_01krdgeqcxet5s7t44vh8rt9mg from: $ref: '#/components/schemas/EmailAddress' description: 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. audience_id: $ref: '#/components/schemas/AudienceID' description: 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. template: oneOf: - $ref: '#/components/schemas/EmailBroadcastTemplate' - type: 'null' description: 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. html_bytes: type: integer minimum: 0 readOnly: true format: int64 example: 18432 description: '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. ' text_bytes: type: integer minimum: 0 readOnly: true format: int64 example: 2104 description: '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`. ' category: type: string minLength: 1 enum: - marketing - transactional description: 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. ip_pool_id: type: string pattern: ^ipp_([0-9a-hjkmnp-tv-z]{26}|shared)$ description: 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. reply_to: type: array items: $ref: '#/components/schemas/EmailAddress' maxItems: 25 description: Where replies to this broadcast go, if you want them somewhere other than the `from` address. Absent when you have not set one. headers: type: object additionalProperties: type: string description: 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. status: readOnly: true example: sent description: '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. ' allOf: - $ref: '#/components/schemas/EmailBroadcastStatus' next: type: array readOnly: true description: '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. ' items: $ref: '#/components/schemas/NextAction' failure_reason: type: - string - 'null' readOnly: true enum: - empty_audience - audience_unavailable - content_invalid - insufficient_funds - quota_exceeded - internal_error - null example: null description: "Why the broadcast failed. Set when `status` is `failed`, and `null` the rest of the time.\n\n- `empty_audience`: There was nobody to send to. Either the audience has no members, or every address in it is suppressed.\n- `audience_unavailable`: The audience no longer exists, so there was nothing to resolve.\n- `content_invalid`: The broadcast could not be set up to send. `failure_detail` says exactly what was wrong. It is one of these:\n - The broadcast has no template, or its template has been deleted.\n - The template has no published version, or no sendable content.\n - The template uses a loop that a broadcast cannot fill.\n - The template requires every send to name a language.\n - The sending domain is no longer verified.\n - The IP pool has nothing to send from.\n - The message could not be handed off for delivery.\n- `insufficient_funds`: There was not enough in the workspace balance to pay for the send.\n- `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.\n- `internal_error`: Something went wrong on our side. Retry, and open a support ticket if it keeps happening.\n" failure_detail: type: - string - 'null' readOnly: true example: null description: 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. recipient_count: type: integer format: int64 minimum: 0 readOnly: true default: 0 example: 4820 description: Number of recipients after suppressed addresses are removed from the audience. This is 0 until sending starts and the audience becomes a recipient list. sent_count: type: integer format: int64 minimum: 0 readOnly: true example: 4820 description: 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. List and single-broadcast reads return 0 when no delivery events are recorded. When counters are unavailable, those reads still return 200 and omit the counters. Read the broadcast again for the numbers. delivered_count: type: integer format: int64 minimum: 0 readOnly: true example: 4712 description: How many recipients' messages were accepted by their mail server. Absent when `sent_count` is. bounced_count: type: integer format: int64 minimum: 0 readOnly: true example: 96 description: How many recipients the message could not be delivered to at all. Absent when `sent_count` is. complained_count: type: integer format: int64 minimum: 0 readOnly: true example: 12 description: How many recipients marked the message as spam. Absent when `sent_count` is. open_count: type: integer format: int64 minimum: 0 readOnly: true example: 3104 description: How many times the message was opened, added up across every recipient. One recipient opening it twice counts twice. Absent when `sent_count` is. click_count: type: integer format: int64 minimum: 0 readOnly: true example: 812 description: 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. sending_ips: type: array readOnly: true items: type: string minLength: 1 example: - 198.51.100.42 description: '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. ' unique_opens_non_prefetched: type: integer format: int64 minimum: 0 readOnly: true example: 2140 description: 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. unique_clicks: type: integer format: int64 minimum: 0 readOnly: true example: 693 description: 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. out_of_band_bounces: type: integer format: int64 minimum: 0 readOnly: true example: 14 description: 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. delivered_recipients: type: integer format: int64 minimum: 0 readOnly: true example: 4724 description: '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.' tags: type: array items: $ref: '#/components/schemas/Tag' description: 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. metadata: type: object description: 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. additionalProperties: true track_opens: type: boolean example: true description: Whether opens are tracked for this broadcast. track_clicks: type: boolean example: true description: Whether link clicks are tracked for this broadcast. created_at: type: string format: date-time minLength: 1 readOnly: true example: '2026-09-01T09:14:02.418Z' description: When the broadcast was created. scheduled_at: type: string format: date-time readOnly: true example: '2026-09-02T08:00:00Z' description: When the broadcast is due to send, and absent when it is not scheduled. started_at: type: string format: date-time readOnly: true example: '2026-09-02T08:00:03.771Z' description: When the broadcast started sending. Absent until then. Compare with `sent_at`, which is when the broadcast finished sending. sent_at: type: - string - 'null' format: date-time readOnly: true example: '2026-09-02T08:11:47.902Z' description: 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. canceled_at: type: string format: date-time readOnly: true description: 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`. ErrorDetail: type: object additionalProperties: false required: - param - message properties: param: type: string minLength: 1 description: 'Dotted field path, such as `to[0].email`, `subject`, or `.`. When the request was rejected for a query parameter the endpoint does not declare, this carries that parameter''s name instead of a field path. ' message: type: string minLength: 1 description: What is wrong with this field. EmailAddressInput: description: 'A sender or recipient address. Accepts a plain email string (`jane@acme.com`), an RFC 5322 mailbox string with an embedded display name (`Jane Doe `), or an object carrying the address and an optional display name. All forms can be mixed freely within one request. Responses always return the object form. ' oneOf: - type: string minLength: 5 maxLength: 998 pattern: ^[^\r\n]+$ title: Email string description: Email address, optionally in RFC 5322 mailbox form with an embedded display name. example: Jane Doe - $ref: '#/components/schemas/EmailAddress' Tag: type: object additionalProperties: false required: - name - value description: 'Structured key/value label attached to a message or a call. Use tags for low-cardinality filtering dimensions (category, experiment ID, template ID); they surface in the list filter of whatever carries them. On a message they also surface in the event log and in webhook payloads, and a message can carry `metadata` beside them for arbitrary per-send context that does not need to be filterable. A call has none of those three: its tags are set on the wire when the call is placed, and the call record is the one place you read them back. Whatever carries the tags defines how many it may have. Tag names are unique: a send that repeats one is rejected, and on a call the first instance of a name wins. ' properties: name: type: string minLength: 1 maxLength: 32 pattern: ^[A-Za-z0-9_-]+$ description: 'Tag name. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 32 characters. ' example: category value: type: string minLength: 1 maxLength: 64 pattern: ^[A-Za-z0-9_-]+$ description: 'Tag value. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 64 characters. ' example: welcome NextAction: type: object additionalProperties: false required: - kind - description properties: kind: type: string minLength: 1 x-extensible-enum: - operation - external - wait - terminal description: "What you do about this step.\n\n- `operation`: call the operation named in `operation`, then\n read again.\n- `external`: act somewhere this API does not reach, then read\n again.\n- `wait`: nothing is asked of you, so read again later.\n- `terminal`: nothing you do resolves this, so stop retrying.\n\nTolerate a value you do not recognize: show the `description` and\noffer no action.\n" description: type: string minLength: 1 description: A short, human-readable label for the step, suitable for display. operation: type: string minLength: 1 description: '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. ' params: type: object additionalProperties: type: string minLength: 1 description: '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. ' url: type: string format: uri description: '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. ' EmailRecipientList: allOf: - type: object required: - data properties: data: type: array description: Page of recipient objects for this email send. items: $ref: '#/components/schemas/EmailRecipient' next: type: array readOnly: true description: 'What to do next, given what this page reports. Present only where the read computes it: an empty list means the answer you were looking for is here and there is nothing further to do. Absent entirely on reads that do not report next actions. ' items: $ref: '#/components/schemas/NextAction' - $ref: '#/components/schemas/_ListEnvelope' EmailBroadcastCreateRequest: type: object additionalProperties: false description: 'A broadcast sends one email to a whole audience. Every field here is optional, so you can create an empty draft and fill it in later. To actually send, a broadcast needs three things: a `from` address on a verified domain, an `audience_id`, and a `template`. Leave `send` false, which is the default, and you get a draft. Update it as often as you like, then send it when you are ready. Set `send` to true and the broadcast goes out as soon as it is created, or at `scheduled_at` if you set one. ' properties: from: $ref: '#/components/schemas/EmailAddressInput' description: The address the broadcast sends from. Give it as a plain address, as `Jane ` to include a display name, or as an object with an address and a name. The domain has to be one this workspace has verified. audience_id: $ref: '#/components/schemas/AudienceID' description: The audience this broadcast sends to. We take the audience's contacts as they stand when the send starts and drop any suppressed addresses, and what is left is who gets the email. template: $ref: '#/components/schemas/EmailBroadcastTemplate' description: The template the broadcast sends. You can leave it out on a draft, but a broadcast cannot send without one. The template's published version is fixed when the broadcast is prepared for sending, and each recipient's contact properties are filled into the content as the email goes out. reply_to: type: array items: $ref: '#/components/schemas/EmailAddressInput' minItems: 1 maxItems: 25 description: Where replies to this broadcast should go. Give each address as a plain address, as `Jane ` to include a display name, or as an object with an address and a name. You can list more than one. headers: type: object maxProperties: 25 additionalProperties: type: string maxLength: 998 description: 'Custom email headers to set on the broadcast, as name and value pairs. Up to 25 of them, each value up to 998 characters. Two names are ours and cannot be set here: `List-Unsubscribe` and `List-Unsubscribe-Post` are dropped if you send them, whatever the category. We add the one-click unsubscribe pair to a marketing broadcast ourselves, and a transactional broadcast has neither header. ' tags: type: array items: $ref: '#/components/schemas/Tag' maxItems: 20 description: Labels on this broadcast, each one a `name` and a `value`, up to 20 of them. You can filter the broadcast list by a tag, break your stats down by one, and read them back off webhook payloads. Use tags for anything you want to find broadcasts by later, and `metadata` for data you only want handed back to you. metadata: type: object description: Any JSON you want to keep on the broadcast. We store it, hand it back when you read the broadcast, and include it in webhook payloads, and you can break stats down by a path inside it such as `metadata.order_id`. It can be up to 2 KB once serialized. additionalProperties: true track_opens: type: boolean default: true description: Whether to track opens for this broadcast. track_clicks: type: boolean default: true description: Whether to track link clicks for this broadcast. ip_pool_id: type: string pattern: ^ipp_([0-9a-hjkmnp-tv-z]{26}|shared)$ description: The IP pool to send this broadcast from. Pass a pool ID, or `ipp_shared` to send through the shared pool on purpose. Leave it out and the broadcast uses your organization's default pool. A pool we do not recognize, or one with no IPs available to send from, is refused with a `422`. category: type: string enum: - marketing - transactional default: marketing description: 'What kind of email this is. A broadcast sets this itself rather than taking it from its template, and it decides two things: which suppressions apply, and whether we add an unsubscribe header. `marketing`, the default, is held back from every suppressed address and has the one-click unsubscribe headers. `transactional` still goes to addresses suppressed for a complaint or an unsubscribe, and has no unsubscribe header. Only use `transactional` for genuine operational mail such as a terms-of-service update or a service outage notice. Marketing content sent this way still reaches people who have already unsubscribed from you. ' send: type: boolean default: false description: Whether to send the broadcast as soon as it is created. Set it to true and the broadcast goes out immediately, or at `scheduled_at` if you set one. Leave it false, which is the default, and you get a draft you can update and send later. scheduled_at: type: string format: date-time description: 'When to send the broadcast. It has to be at least 30 seconds and at most 365 days from now. It requires `send` to be true, so a `scheduled_at` on its own is refused rather than saved on the draft. ' example: from: newsletter@acme.com audience_id: adn_01krdgeqcxet5s7t44vh8rt9mg template: id: emt_01krdgeqcxet5s7t44vh8rt9mg category: marketing tags: - name: campaign value: spring_launch metadata: campaign_id: '12345' EmailBroadcastSendQuota: type: object additionalProperties: false required: - recipients - allowed - limited_by description: 'How much of a broadcast the organization''s email send allowance covers, read before the broadcast is sent. The allowance is shared with every other email the organization sends, so `allowed` moves as those sends land, and `recipients` moves as audience membership and suppressions change. Treat both as a live estimate rather than a promise: a broadcast whose recipients all fit today can still run into the allowance if other sends consume it first. ' properties: recipients: type: integer format: int64 minimum: 0 readOnly: true example: 12000 description: 'Number of contacts the broadcast would send to right now, after contacts without an email address and contacts suppressed for the broadcast''s category are dropped. The same number the broadcast''s audience counts report as sendable. ' allowed: type: integer format: int64 minimum: 0 readOnly: true example: 5000 description: 'Number of those recipients the organization''s email send allowance covers. Equal to `recipients` when nothing limits the send, and lower when part of the audience runs past what is left of it. A part-covered send goes out in whole batches, so this is cut back to a batch boundary rather than to the exact number of emails left: it can sit below `remaining` rather than matching it, and should be read rather than worked out from `limit` and `remaining`. 0 means none of them would go out, either because the audience is larger than the whole allowance, which is refused rather than sent in part, or because too little of the allowance is left to carry any of it. ' limited_by: readOnly: true example: monthly $ref: '#/components/schemas/EmailSendAllowanceWindow' limit: type: integer format: int64 minimum: 0 readOnly: true example: 50000 description: 'Size of the allowance named by `limited_by`, in emails. Omitted when nothing limits the send. ' remaining: type: integer format: int64 minimum: 0 readOnly: true example: 7000 description: 'How much of that allowance is left in the current window, in emails. Omitted when nothing limits the send. ' EmailBroadcastUpdateRequest: type: object additionalProperties: false description: 'Changes a broadcast that is still a draft or is scheduled. Whatever you send here is applied, and anything you leave out keeps the value it already had. Once a broadcast has started sending it can no longer be edited. ' properties: from: $ref: '#/components/schemas/EmailAddressInput' description: The address the broadcast sends from. Give it as a plain address, as `Jane ` to include a display name, or as an object with an address and a name. The domain has to be one this workspace has verified. audience_id: $ref: '#/components/schemas/AudienceID' description: The audience this broadcast sends to. We take the audience's contacts as they stand when the send starts and drop any suppressed addresses, and what is left is who gets the email. template: oneOf: - $ref: '#/components/schemas/EmailBroadcastTemplate' - type: 'null' description: The template the broadcast sends. Its published version is fixed when the broadcast is prepared for sending. Set this to null to take the template off a draft, or leave it out to keep the one already set. reply_to: type: - array - 'null' items: $ref: '#/components/schemas/EmailAddressInput' minItems: 1 maxItems: 25 description: Where replies to this broadcast should go. Set this to null to remove the addresses already set. headers: type: object maxProperties: 25 additionalProperties: type: string maxLength: 998 description: 'Custom email headers to set on the broadcast, as name and value pairs. What you send replaces the headers the draft already had rather than adding to them. Up to 25 of them, each value up to 998 characters. Two names are ours and cannot be set here: `List-Unsubscribe` and `List-Unsubscribe-Post` are dropped if you send them, whatever the category. We add the one-click unsubscribe pair to a marketing broadcast ourselves, and a transactional broadcast has neither header. ' tags: type: array items: $ref: '#/components/schemas/Tag' maxItems: 20 description: Labels on this broadcast, each one a `name` and a `value`, that you can filter and search broadcasts by. What you send replaces the tags the draft already had rather than adding to them. metadata: type: object description: Any JSON you want to keep on the broadcast, up to 2 KB once serialized. What you send replaces the metadata the draft already had rather than merging into it. additionalProperties: true track_opens: type: boolean description: Whether to track opens for this broadcast. track_clicks: type: boolean description: Whether to track link clicks for this broadcast. ip_pool_id: type: - string - 'null' pattern: ^ipp_([0-9a-hjkmnp-tv-z]{26}|shared)$ description: The IP pool to send this broadcast from. Pass a pool ID, or `ipp_shared` to send through the shared pool on purpose. Set it to null to fall back to your organization's default pool. category: type: string enum: - marketing - transactional description: 'What kind of email this is. It decides two things: which suppressions apply, and whether we add an unsubscribe header. `marketing` is held back from every suppressed address and has the one-click unsubscribe headers. `transactional` still goes to addresses suppressed for a complaint or an unsubscribe, and has no unsubscribe header. Only use `transactional` for genuine operational mail such as a terms-of-service update or a service outage notice. Marketing content sent this way reaches people who have already unsubscribed from you. ' example: category: marketing template: id: emt_01krdgeqcxet5s7t44vh8rt9mg Error: type: object additionalProperties: false required: - error properties: error: $ref: '#/components/schemas/ErrorBody' EmailBroadcastList: allOf: - type: object required: - data properties: data: type: array description: Page of broadcast objects. items: $ref: '#/components/schemas/EmailBroadcast' - $ref: '#/components/schemas/_ListEnvelope' EmailRecipient: type: object additionalProperties: false required: - id - parent_id - role - recipient - status - open_count - click_count properties: id: readOnly: true $ref: '#/components/schemas/RecipientID' description: Recipient ID. parent_id: $ref: '#/components/schemas/EmailID' description: ID of the message this recipient belongs to. For a message send, this is the message's own `em_`-prefixed ID. For a broadcast, it is the `em_`-prefixed ID of the copy addressed to this recipient. Read either one with [Get an email message](/docs/api/reference/get-email-message), which answers 404 for a broadcast copy the send has not recorded. No recipient status distinguishes a copy the send recorded from one it did not. role: $ref: '#/components/schemas/RecipientRole' description: How this recipient appeared in the send request. recipient: type: string format: email minLength: 5 description: Recipient email address. name: type: - string - 'null' description: Display name provided for this recipient on the send, or null if none was given. status: type: string minLength: 1 readOnly: true enum: - accepted - processed - deferred - delivered - bounced - complained - rejected x-enum-varnames: - EmailRecipientStatusAccepted - EmailRecipientStatusProcessed - EmailRecipientStatusDeferred - EmailRecipientStatusDelivered - EmailRecipientStatusBounced - EmailRecipientStatusComplained - EmailRecipientStatusRejected description: 'Delivery status for this recipient: - `accepted`: The send has been taken and is being prepared for delivery. - `processed`: This recipient''s message is on its way out. - `deferred`: The recipient''s mailbox provider asked for a retry, and delivery attempts continue. - `delivered`: The recipient''s mail server accepted the message. - `bounced`: Delivery permanently failed (see `bounce_type` for hard vs soft). - `complained`: The recipient reported the message as spam. - `rejected`: Delivery was never attempted (see `rejection_reason` for why). ' rejection_reason: type: - string - 'null' readOnly: true enum: - recipient_suppressed - transmission_failed - generation_failure - policy_rejection - domain_unverified - quota_exceeded - recipient_not_allowed - null description: "Present on `status: rejected` rows. Specifies why the recipient was rejected:\n\n- `recipient_suppressed`: The recipient is on the workspace suppression list, so\n delivery was never attempted.\n- `transmission_failed`: The message could not be transmitted for delivery.\n- `generation_failure`: The message could not be built for delivery (template or\n content issue).\n- `policy_rejection`: The message was refused by sending policy.\n- `domain_unverified`: The sending domain was not verified.\n- `quota_exceeded`: The organization's send quota was reached.\n- `recipient_not_allowed`: A recipient was not permitted for this send (for shared\n onboarding-domain sends, recipients must be verified workspace members).\n" bounce_type: type: - string - 'null' readOnly: true enum: - hard - soft - undetermined - admin - block - null description: 'Bounce classification for `bounced` and `deferred` rows, or null when the recipient has not bounced or the receiving server''s response has not been classified. - `hard`: a permanent failure (invalid address or non-existent domain). - `soft`: a transient failure (mailbox full or server temporarily unavailable). - `block`: the receiving mail server blocked the sending IP for reputation reasons. - `admin`: an administrative refusal (relaying denied or blocklisted domain). - `undetermined`: the receiving server''s response is ambiguous. ' bounce_code: type: - string - 'null' readOnly: true description: SMTP reply code returned by the receiving mail server for `bounced` and `deferred` rows, or null when none was provided. example: '550' bounce_description: type: - string - 'null' readOnly: true description: Human-readable reason the receiving mail server gave for the bounce or deferral, or null when none was provided. example: 5.1.1 Unknown user processed_at: type: - string - 'null' format: date-time readOnly: true description: When the message was prepared and queued for delivery to the recipient's mail server, or null if that has not happened yet. delivered_at: type: - string - 'null' format: date-time readOnly: true description: When the recipient's mail server accepted the message, or null if not yet delivered. processing_latency_ms: type: - integer - 'null' minimum: 0 readOnly: true description: Time between the send being accepted and the message being prepared for delivery, in milliseconds. Null until processed. delivery_latency_ms: type: - integer - 'null' minimum: 0 readOnly: true description: Time between the message being prepared and the receiving mail server accepting it, in milliseconds. Null until delivered. total_latency_ms: type: - integer - 'null' minimum: 0 readOnly: true description: End-to-end accept → delivered time for this recipient, in milliseconds. Null until delivered. open_count: type: integer readOnly: true default: 0 description: Number of open events for this recipient. click_count: type: integer readOnly: true default: 0 description: Number of click events for this recipient. EmailID: type: string minLength: 1 pattern: ^em_[0-9a-hjkmnp-tv-z]{26}$ example: em_01krdgeqcxet5s7t44vh8rt9mg RecipientRole: type: string minLength: 1 enum: - to - cc - bcc description: Envelope position of a recipient on an outbound email event. example: to EmailBroadcastStatus: type: string minLength: 1 enum: - draft - scheduled - accepted - sending - sent - canceling - canceled - failed description: 'Where the broadcast itself has got to. This is separate from what happened to individual recipients, which the broadcast''s own `sent_count`, `delivered_count`, `bounced_count` and `complained_count` tell you. Those four are fields on the broadcast, not on everything that carries this status, and reading the broadcast''s recipients or its events gives the same outcomes one recipient at a time. - `draft`: Created, and not sent or scheduled yet. - `scheduled`: Due to send at `scheduled_at`. You can still edit it, and you can still change the time, right up until sending starts. - `accepted`: Taken for immediate sending. Nothing has gone out yet. - `sending`: On its way. Some recipients have been sent to and some have not. - `sent`: Every recipient has been sent to. - `canceling`: A cancellation is under way and the remaining sends are stopping. - `canceled`: The cancellation finished. Anything already on its way to a recipient when you canceled cannot be pulled back. - `failed`: The broadcast could not be sent. Reading the broadcast gives `failure_reason`, which says why. If it had already started sending, the recipients it reached keep their delivery status and carry on producing events. A draft is deleted rather than canceled, because it was never sent. ' EmailAddress: type: object additionalProperties: false description: An email address with an optional display name. required: - email properties: email: type: string format: email minLength: 5 description: Email address. example: jane@acme.com name: type: string minLength: 1 maxLength: 256 pattern: ^[^\r\n]+$ description: Display name shown alongside the address in mail clients. example: Jane Doe responses: InternalError: description: Internal server error content: application/json: schema: $ref: '#/components/schemas/Error' PaymentRequired: description: Insufficient balance content: application/json: schema: $ref: '#/components/schemas/Error' RateLimited: description: Rate limit exceeded headers: RateLimit: $ref: '#/components/headers/RateLimit' RateLimit-Policy: $ref: '#/components/headers/RateLimit-Policy' Retry-After: $ref: '#/components/headers/RetryAfter' content: application/json: schema: $ref: '#/components/schemas/Error' Forbidden: description: Insufficient permissions content: application/json: schema: $ref: '#/components/schemas/Error' ServiceUnavailable: description: 'The service is temporarily unavailable. If `Retry-After` is present, wait for that delay before retrying; otherwise, retry with exponential backoff. Reuse the same idempotency key and request when retrying a mutation. ' headers: Retry-After: $ref: '#/components/headers/RetryAfter' content: application/json: schema: $ref: '#/components/schemas/Error' Conflict: description: Resource conflict content: application/json: schema: $ref: '#/components/schemas/Error' Unprocessable: description: 'The request has invalid field values, violates a business rule, or carries a query parameter the endpoint does not declare. Field validation errors use `type: validation_error` and include the affected fields in `details`. Business-rule errors identify the failed rule in `type`. ' content: application/json: schema: $ref: '#/components/schemas/Error' BadRequest: description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/Error' Unauthorized: description: Authentication required content: application/json: schema: $ref: '#/components/schemas/Error' headers: RetryAfter: description: 'Number of seconds to wait before retrying the request. ' schema: type: integer minimum: 0 example: 35 IdempotencyReplay: description: The API includes this header when it replays the response for an earlier request that used the same `Idempotency-Key`. The API does not process the request again. schema: type: string enum: - 'true' RateLimit: description: 'Remaining capacity for the request''s rate-limit policy as an IETF Structured Field. Format: `"";r=;t=`. ' schema: type: string example: '"email_send";r=842;t=35' RateLimit-Policy: description: 'Effective quota for the request''s rate-limit policy as an IETF Structured Field. Format: `"";q=;w=`. ' schema: type: string example: '"email_send";q=1000;w=60' ContentDisposition: description: 'Indicates the response is a file attachment and carries the suggested download filename, for example `attachment; filename="invoice-2600042.pdf"`. ' schema: type: string securitySchemes: BearerAuth: type: http scheme: bearer description: 'Pass the API key as a bearer token in the `Authorization` header. Keys use the format `bk_{region}_*`. The prefix identifies the region and selects the API endpoint. Official Bird SDKs and the CLI derive the region from the key. ' CookieAuth: type: apiKey in: cookie name: bird_session description: 'Session cookie set after signing in to the Bird dashboard. The cookie value is an opaque session token; no session data is stored in the cookie itself. ' RealtimeKey: type: apiKey in: header name: X-Realtime-Key description: 'The Realtime app key. Together with `X-Realtime-Secret`, it authenticates a request to the Realtime API in addition to the workspace credential. Both values come from the app''s credentials and must belong to the calling workspace. Official Bird SDKs accept the pair as client configuration. ' RealtimeSecret: type: apiKey in: header name: X-Realtime-Secret description: 'The Realtime app secret paired with `X-Realtime-Key`. The API returns the secret only when the key is created and does not store it. Create a new key and revoke the current key if you lose the secret. Official Bird SDKs accept the pair as client configuration. '