openapi: 3.2.0 info: title: Bird Whatsapp Stats 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: whatsapp-stats description: WhatsApp analytics, including daily and hourly delivery statistics and a KPI summary. paths: /v1/whatsapp/stats/summary: get: operationId: getWhatsAppStatsSummary x-snippet-key: whatsapp.stats.summary summary: Get aggregate outbound WhatsApp statistics description: 'Returns one aggregate row for the requested period. It includes WhatsApp lifecycle counts, delivery and failure rates, and processing, delivery, and total latency percentiles (`p50`, `p95`, and `p99`). Rows use send-time attribution, so recent periods can under-report `delivered` while delivery reports arrive. Rate fields are `null` when their denominator is zero. `from` and `to` must both be calendar days or RFC 3339 instants. Day windows cover up to 365 whole days. Instant bounds round down to the hour, remain inclusive, and may span up to 720 hours. Mixing the forms returns `422`. Set `timezone` for local boundaries. Set one dimension filter at most; more than one returns `422`. Use `compare=previous_period` to include the preceding equal-length window and each metric''s change.' tags: - whatsapp-stats x-audiences: - public - command security: - BearerAuth: [] - CookieAuth: [] parameters: - name: from in: query required: false description: 'Inclusive start of the window: a calendar day (YYYY-MM-DD) or an RFC 3339 instant rounded down to the hour. The `timezone` parameter makes a calendar day local and rounds an instant down to the local hour. Omit `timezone` to use UTC. When `timezone` is set, a numeric UTC offset such as `+05:45` is rejected; use a calendar day or a `Z` (UTC) instant. This value must use the same form as `to`. When omitted, it defaults to 30 days before `to` for day windows or 168 hours (7 days) before `to` for hour windows. ' schema: type: string minLength: 1 pattern: ^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2}))?$ example: '2026-05-01' - name: to in: query required: false description: 'Inclusive end of the window: a calendar day (YYYY-MM-DD) or an RFC 3339 instant rounded down to the hour. The `timezone` parameter makes a calendar day local and rounds an instant down to the local hour. Omit `timezone` to use UTC. When `timezone` is set, a numeric UTC offset is rejected; use a calendar day or a `Z` (UTC) instant. This value must use the same form as `from`. When omitted, it defaults to today for day windows or the current hour for hour windows in that timezone. Day windows may not exceed 365 days; hour windows may not exceed 720 hours (30 days). ' schema: type: string minLength: 1 pattern: ^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2}))?$ example: '2026-05-25' - $ref: '#/components/parameters/StatsTimezone' - $ref: '#/components/parameters/WhatsAppStatsTemplateFilter' - name: category in: query required: false description: 'Restrict the statistics to a single template category. Mutually exclusive with the other dimension filters (`template`, `phone_number`, `tag`); only one may be set per request. Matches the `category` key on a row of the per-category breakdown. ' schema: $ref: '#/components/schemas/WhatsAppTemplateCategory' - name: phone_number in: query required: false description: 'Restrict the statistics to a single business sender phone number, in E.164 form. Mutually exclusive with the other dimension filters (`template`, `category`, `tag`); only one may be set per request. Matches the `phone_number` key on a row of the per-phone-number breakdown. ' schema: type: string minLength: 1 pattern: ^\+[1-9]\d{1,14}$ example: '+13124495569' - name: tag in: query required: false description: 'Restrict the statistics to a single tag. Use `name` to match any value of a tag, or `name:value` for a specific pair (for example `campaign:spring_launch`). Mutually exclusive with the other dimension filters (`template`, `category`, `phone_number`); only one may be set per request. A row of the per-tag breakdown carries the same pair in its single `tag` key. ' schema: type: string minLength: 1 example: campaign:spring_launch - name: compare in: query required: false description: 'Set to `previous_period` to also include the same statistics for the immediately preceding window of equal length, plus the change between the two, so you can show "+X% vs last period" without a second request. The comparison window carries any dimension filter set on the request, so a filtered comparison compares like with like. ' schema: $ref: '#/components/schemas/StatsComparePeriod' responses: '200': description: Aggregate summary for the requested period. content: application/json: schema: $ref: '#/components/schemas/WhatsAppStatsSummary' '400': $ref: '#/components/responses/BadRequest' '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' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk /v1/whatsapp/stats/daily: get: operationId: getWhatsAppStatsDaily x-snippet-key: whatsapp.stats.daily summary: Get daily outbound WhatsApp statistics description: 'Returns one row of aggregate WhatsApp statistics per calendar day for the workspace. Rows use send-time attribution, so a delivery confirmation received on Wednesday for a message accepted the prior Monday is counted in Monday''s row. Recent rows can under-report `delivered` while delivery reports arrive. Days with no activity are included with zero counts. Each row carries lifecycle counts (accepted, sent, delivered, read, failed) and its own latency percentiles; delivery, failure, and read rates are whole-window aggregates available from the summary endpoint instead. `from` and `to` are optional calendar days (YYYY-MM-DD), defaulting to the trailing 30 days. The maximum window is 365 days; a longer range returns `422`. Set `timezone` to report rows by your local calendar day. Set at most one dimension filter (`template`, `category`, `phone_number`, `tag`) to restrict the statistics to that dimension''s value; setting more than one returns 422.' tags: - whatsapp-stats x-audiences: - public - command security: - BearerAuth: [] - CookieAuth: [] parameters: - name: from in: query required: false description: Start date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). The window may not exceed 365 days. Defaults to 30 days before `to` when omitted. schema: type: string format: date minLength: 1 example: '2026-05-01' - name: to in: query required: false description: End date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). The window may not exceed 365 days. Defaults to today in that timezone when omitted. schema: type: string format: date minLength: 1 example: '2026-05-25' - $ref: '#/components/parameters/StatsTimezone' - $ref: '#/components/parameters/WhatsAppStatsTemplateFilter' - name: category in: query required: false description: 'Restrict the statistics to a single template category. Mutually exclusive with the other dimension filters (`template`, `phone_number`, `tag`); only one may be set per request. Matches the `category` key on a row of the per-category breakdown. ' schema: $ref: '#/components/schemas/WhatsAppTemplateCategory' - name: phone_number in: query required: false description: 'Restrict the statistics to a single business sender phone number, in E.164 form. Mutually exclusive with the other dimension filters (`template`, `category`, `tag`); only one may be set per request. Matches the `phone_number` key on a row of the per-phone-number breakdown. ' schema: type: string minLength: 1 pattern: ^\+[1-9]\d{1,14}$ example: '+13124495569' - name: tag in: query required: false description: 'Restrict the statistics to a single tag. Use `name` to match any value of a tag, or `name:value` for a specific pair (for example `campaign:spring_launch`). Mutually exclusive with the other dimension filters (`template`, `category`, `phone_number`); only one may be set per request. A row of the per-tag breakdown carries the same pair in its single `tag` key. ' schema: type: string minLength: 1 example: campaign:spring_launch responses: '200': description: Daily aggregate stats for the requested period. content: application/json: schema: $ref: '#/components/schemas/WhatsAppStatsResponse' '400': $ref: '#/components/responses/BadRequest' '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' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk /v1/whatsapp/stats/hourly: get: operationId: getWhatsAppStatsHourly x-snippet-key: whatsapp.stats.hourly summary: Get hourly outbound WhatsApp statistics description: 'Returns one row of WhatsApp lifecycle counts per hour. Rows use send-time attribution, so a delivery confirmation is counted in the hour when its message was accepted. Recent rows can under-report `delivered` while delivery reports arrive. Set `timezone` for local hours instead of UTC, including zones with sub-hour offsets. Each row includes its own latency percentiles; delivery, failure, and read rates are whole-window aggregates available from the summary endpoint instead. `from` and `to` are optional RFC 3339 instants, defaulting to the trailing 168 hours; each bound rounds down to the hour and remains inclusive. A request may span up to 30 days (720 rows). An excessive or reversed window returns `422`. Set one dimension filter at most; more than one returns `422`.' tags: - whatsapp-stats x-audiences: - public - command security: - BearerAuth: [] - CookieAuth: [] parameters: - name: from in: query required: false description: Start of the window (RFC 3339 instant), rounded down to the start of its hour and included. The boundary uses the local hour when `timezone` is set and the UTC hour otherwise. The window may not exceed 30 days (720 hours). Defaults to 168 hours (7 days) before `to` when omitted. schema: type: string format: date-time minLength: 1 example: '2026-05-25T00:00:00Z' - name: to in: query required: false description: End of the window (RFC 3339 instant), rounded down to the start of its hour and included. The boundary uses the local hour when `timezone` is set and the UTC hour otherwise, so both bounds are inclusive. The window may not exceed 30 days (720 hours). Defaults to the current hour when omitted. schema: type: string format: date-time minLength: 1 example: '2026-05-25T23:59:59Z' - $ref: '#/components/parameters/StatsTimezone' - $ref: '#/components/parameters/WhatsAppStatsTemplateFilter' - name: category in: query required: false description: 'Restrict the statistics to a single template category. Mutually exclusive with the other dimension filters (`template`, `phone_number`, `tag`); only one may be set per request. Matches the `category` key on a row of the per-category breakdown. ' schema: $ref: '#/components/schemas/WhatsAppTemplateCategory' - name: phone_number in: query required: false description: 'Restrict the statistics to a single business sender phone number, in E.164 form. Mutually exclusive with the other dimension filters (`template`, `category`, `tag`); only one may be set per request. Matches the `phone_number` key on a row of the per-phone-number breakdown. ' schema: type: string minLength: 1 pattern: ^\+[1-9]\d{1,14}$ example: '+13124495569' - name: tag in: query required: false description: 'Restrict the statistics to a single tag. Use `name` to match any value of a tag, or `name:value` for a specific pair (for example `campaign:spring_launch`). Mutually exclusive with the other dimension filters (`template`, `category`, `phone_number`); only one may be set per request. A row of the per-tag breakdown carries the same pair in its single `tag` key. ' schema: type: string minLength: 1 example: campaign:spring_launch responses: '200': description: Hourly aggregate stats for the requested period. content: application/json: schema: $ref: '#/components/schemas/WhatsAppStatsResponse' '400': $ref: '#/components/responses/BadRequest' '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' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk /v1/whatsapp/stats/error-codes: get: operationId: getWhatsAppStatsByErrorCode x-snippet-key: whatsapp.stats.by_error_code summary: Get outbound WhatsApp statistics by error code description: Returns the count of failed WhatsApp messages grouped by normalized failure reason for the requested period. Rows are ranked by failure count descending and capped at the requested `limit` (default 50, max 200). Rows use send-time attribution, so a failure reported during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports failures while reports are still arriving, and its counts grow as they land. The maximum window is 365 days; a longer range returns 422. A breakdown is already a single-dimension view and takes no dimension filter; to restrict statistics to a single template, category, phone number or tag, use the summary, daily or hourly statistics instead. tags: - whatsapp-stats x-audiences: - public - command security: - BearerAuth: [] - CookieAuth: [] parameters: - name: from in: query required: false description: Inclusive start of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Must not be after `to`. Max window 365 days. Defaults to 30 days before `to` when omitted. schema: type: string format: date minLength: 1 example: '2026-06-14' - name: to in: query required: false description: Inclusive end of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Max window 365 days. Defaults to today in that timezone when omitted. schema: type: string format: date minLength: 1 example: '2026-07-13' - $ref: '#/components/parameters/StatsTimezone' - name: limit in: query required: false description: Maximum number of error-code rows to return, ranked by failure count descending. schema: type: integer minimum: 1 maximum: 200 default: 50 responses: '200': description: Per-error-code failure breakdown for the requested period. content: application/json: schema: $ref: '#/components/schemas/WhatsAppStatsByErrorCodeResponse' '400': $ref: '#/components/responses/BadRequest' '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' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk /v1/whatsapp/stats/templates: get: operationId: getWhatsAppStatsByTemplate x-snippet-key: whatsapp.stats.by_template summary: Get outbound WhatsApp statistics by template description: 'Returns lifecycle counts and delivery rates for WhatsApp messages grouped by the template they were sent from, for the requested period. Rows are keyed by `template_id`, matching the email template breakdown, so a renamed template stays one row. Rows are ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200). Rows use send-time attribution. A delivery confirmed during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports `delivered` while delivery reports are still arriving, and its counts grow as reports arrive. The maximum window is 365 days; a longer range returns 422. A breakdown is already a single-dimension view and takes no dimension filter; to restrict statistics to a single template, category, phone number or tag, use the summary, daily or hourly statistics instead. For the coarser split across Meta''s marketing, utility, and authentication categories, use the template-categories breakdown instead. Each row also carries the same three latency families as the summary: `processing`, `delivery` and `total`. `delivery` is measured from a best-effort handoff timestamp that a fast delivery callback can beat, so it can be absent for a row whose other two families are present.' tags: - whatsapp-stats x-audiences: - public - command security: - BearerAuth: [] - CookieAuth: [] parameters: - name: from in: query required: false description: Inclusive start of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Must not be after `to`. Max window 365 days. Defaults to 30 days before `to` when omitted. schema: type: string format: date minLength: 1 example: '2026-06-14' - name: to in: query required: false description: Inclusive end of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Max window 365 days. Defaults to today in that timezone when omitted. schema: type: string format: date minLength: 1 example: '2026-07-13' - $ref: '#/components/parameters/StatsTimezone' - name: limit in: query required: false description: Maximum number of template rows to return, ranked by accepted volume descending. schema: type: integer minimum: 1 maximum: 200 default: 50 responses: '200': description: Per-template breakdown for the requested period. content: application/json: schema: $ref: '#/components/schemas/WhatsAppStatsByTemplateResponse' '400': $ref: '#/components/responses/BadRequest' '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' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk /v1/whatsapp/stats/template-categories: get: operationId: getWhatsAppStatsByTemplateCategory x-snippet-key: whatsapp.stats.by_template_category summary: Get outbound WhatsApp statistics by template category description: 'Returns lifecycle counts and delivery rates for WhatsApp messages grouped by template category for the requested period. Rows are ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200). Rows use send-time attribution. A delivery confirmed during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports `delivered` while delivery reports are still arriving, and its counts grow as reports arrive. The maximum window is 365 days; a longer range returns 422. A breakdown is already a single-dimension view and takes no dimension filter; to restrict statistics to a single template, category, phone number or tag, use the summary, daily or hourly statistics instead. Each row also carries the same three latency families as the summary: `processing`, `delivery` and `total`. `delivery` is measured from a best-effort handoff timestamp that a fast delivery callback can beat, so it can be absent for a row whose other two families are present.' tags: - whatsapp-stats x-audiences: - public - command security: - BearerAuth: [] - CookieAuth: [] parameters: - name: from in: query required: false description: Inclusive start of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Must not be after `to`. Max window 365 days. Defaults to 30 days before `to` when omitted. schema: type: string format: date minLength: 1 example: '2026-06-14' - name: to in: query required: false description: Inclusive end of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Max window 365 days. Defaults to today in that timezone when omitted. schema: type: string format: date minLength: 1 example: '2026-07-13' - $ref: '#/components/parameters/StatsTimezone' - name: limit in: query required: false description: Maximum number of template-category rows to return, ranked by accepted volume descending. schema: type: integer minimum: 1 maximum: 200 default: 50 responses: '200': description: Per-template-category breakdown for the requested period. content: application/json: schema: $ref: '#/components/schemas/WhatsAppStatsByTemplateCategoryResponse' '400': $ref: '#/components/responses/BadRequest' '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' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk /v1/whatsapp/stats/tags: get: operationId: getWhatsAppStatsByTag x-snippet-key: whatsapp.stats.by_tag summary: Get outbound WhatsApp statistics by tag description: 'Returns lifecycle counts and delivery rates for WhatsApp messages grouped by tag (`name:value`) for the requested period. Rows are ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200). Rows use send-time attribution. A delivery confirmed during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports `delivered` while delivery reports are still arriving, and its counts grow as reports arrive. The maximum window is 365 days; a longer range returns 422. A breakdown is already a single-dimension view and takes no dimension filter; to restrict statistics to a single template, category, phone number or tag, use the summary, daily or hourly statistics instead. Each row also carries the same three latency families as the summary: `processing`, `delivery` and `total`. `delivery` is measured from a best-effort handoff timestamp that a fast delivery callback can beat, so it can be absent for a row whose other two families are present.' tags: - whatsapp-stats x-audiences: - public - command security: - BearerAuth: [] - CookieAuth: [] parameters: - name: from in: query required: false description: Inclusive start of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Must not be after `to`. Max window 365 days. Defaults to 30 days before `to` when omitted. schema: type: string format: date minLength: 1 example: '2026-06-14' - name: to in: query required: false description: Inclusive end of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Max window 365 days. Defaults to today in that timezone when omitted. schema: type: string format: date minLength: 1 example: '2026-07-13' - $ref: '#/components/parameters/StatsTimezone' - name: limit in: query required: false description: Maximum number of tag rows to return, ranked by accepted volume descending. schema: type: integer minimum: 1 maximum: 200 default: 50 responses: '200': description: Per-tag breakdown for the requested period. content: application/json: schema: $ref: '#/components/schemas/WhatsAppStatsByTagResponse' '400': $ref: '#/components/responses/BadRequest' '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' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk /v1/whatsapp/stats/phone-numbers: get: operationId: getWhatsAppStatsByPhoneNumber x-snippet-key: whatsapp.stats.by_phone_number summary: Get outbound WhatsApp statistics by phone number description: 'Returns delivery counts grouped by business phone number, including whether each is platform-managed or customer-owned. Rows use send-time attribution, rank by accepted volume, and are capped by `limit` (default 50, maximum 200). A recent period under-reports `delivered` while delivery reports are still arriving. The maximum window is 365 days; a longer range returns `422`. A breakdown is already a single-dimension view and takes no dimension filter; to restrict statistics to a single template, category, phone number or tag, use the summary, daily or hourly statistics instead. Each row also carries the same three latency families as the summary: `processing`, `delivery` and `total`. `delivery` is measured from a best-effort handoff timestamp that a fast delivery callback can beat, so it can be absent for a row whose other two families are present.' tags: - whatsapp-stats x-audiences: - public - command security: - BearerAuth: [] - CookieAuth: [] parameters: - name: from in: query required: false description: Inclusive start of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Must not be after `to`. Max window 365 days. Defaults to 30 days before `to` when omitted. schema: type: string format: date minLength: 1 example: '2026-06-14' - name: to in: query required: false description: Inclusive end of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Max window 365 days. Defaults to today in that timezone when omitted. schema: type: string format: date minLength: 1 example: '2026-07-13' - $ref: '#/components/parameters/StatsTimezone' - name: limit in: query required: false description: Maximum number of phone-number rows to return, ranked by accepted volume descending. schema: type: integer minimum: 1 maximum: 200 default: 50 responses: '200': description: Per-phone-number breakdown for the requested period. content: application/json: schema: $ref: '#/components/schemas/WhatsAppStatsByPhoneNumberResponse' '400': $ref: '#/components/responses/BadRequest' '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' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk /v1/whatsapp/stats/countries: get: operationId: getWhatsAppStatsByCountry x-snippet-key: whatsapp.stats.by_country summary: Get outbound WhatsApp statistics by country description: 'Returns delivery counts, read engagement and latency grouped by the recipient''s destination country. Rows use send-time attribution, rank by accepted volume, and are capped by `limit` (default 50, maximum 200). A recent period under-reports `delivered` while delivery reports are still arriving. The maximum window is 365 days; a longer range returns `422`. A country row covers messages addressed to a single recipient. A recipient whose country cannot be resolved (a number shaped like a phone number that belongs to no country, or a non-geographic range such as freephone) is counted under `ZZ` rather than dropped, matching the SMS country breakdown. Group sends are omitted entirely: one spans up to eight recipients in as many countries, so no single destination country describes it. These rows therefore sum to the summary less that group volume. A window reaching before this breakdown shipped falls short by more than that: history was seeded only where the recipient''s country could be recovered from stored data, so a phone-addressed send accepted before the cutover has no `accepted` leg here. Two cases follow, and they read differently. Acceptance days more than 14 days before the cutover are a plain shortfall and never change. In the 14 days immediately before it, a `delivered`, `read` or `failed` callback arriving after the cutover does carry a country and is attributed to its original acceptance day, so a row there can report deliveries with `accepted` at zero. Its `delivery_rate` and `failure_rate` are then null for want of a denominator while `read_rate` still computes and looks healthy. The 14 days are the status recording window, after which a callback is dropped. All of this is confined to pre-cutover acceptance days and gone once the requested window starts after the cutover date. Latency reports the same three families as the summary: `processing`, `delivery` and `total`. The delivery family depends on a best-effort handoff timestamp that a fast delivery callback can beat, so it can be absent for a country whose other two families are present. A breakdown is already a single-dimension view and takes no dimension filter; to restrict statistics to a single template, category, phone number or tag, use the summary, daily or hourly statistics instead.' tags: - whatsapp-stats x-audiences: - public - command security: - BearerAuth: [] - CookieAuth: [] parameters: - name: from in: query required: false description: Inclusive start of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Must not be after `to`. Max window 365 days. Defaults to 30 days before `to` when omitted. schema: type: string format: date minLength: 1 example: '2026-08-09' - name: to in: query required: false description: Inclusive end of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Max window 365 days. Defaults to today in that timezone when omitted. schema: type: string format: date minLength: 1 example: '2026-09-08' - $ref: '#/components/parameters/StatsTimezone' - name: limit in: query required: false description: Maximum number of country rows to return, ranked by accepted volume descending. schema: type: integer minimum: 1 maximum: 200 default: 50 responses: '200': description: Per-country breakdown for the requested period. content: application/json: schema: $ref: '#/components/schemas/WhatsAppStatsByCountryResponse' '400': $ref: '#/components/responses/BadRequest' '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' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk /v1/whatsapp/stats/inbound/summary: get: operationId: getWhatsAppInboundStatsSummary x-snippet-key: whatsapp.stats.inbound.summary summary: Get aggregate inbound WhatsApp statistics description: 'Returns the total number of WhatsApp messages your business numbers received over the period, using the time each message reached your number. The response contains only a count because a received message has one state. Use the send statistics endpoints for delivery rates and latency data about messages you send. The maximum window is 365 days for a day-grain range, or 720 hours for an hour-grain range; a longer range returns 422. Set `timezone` to resolve the period against your local calendar instead of UTC.' tags: - whatsapp-stats x-audiences: - public - command security: - BearerAuth: [] - CookieAuth: [] parameters: - name: from in: query required: false description: 'Inclusive start of the window, either a calendar day (YYYY-MM-DD) or an RFC 3339 instant rounded down to the hour. The form you use selects the grain the total is resolved at. Interpreted in `timezone`, or in UTC when `timezone` is omitted. Must use the same form as `to`. A numeric UTC offset (for example `+05:45`) is rejected when `timezone` is set; pass a calendar day or a `Z` instant instead. Defaults to 30 days before `to` for day windows, or 168 hours before `to` for hour windows. ' schema: type: string minLength: 1 pattern: ^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2}))?$ example: '2026-05-01' - name: to in: query required: false description: 'Inclusive end of the window, in the same form as `from`. Defaults to today, or the current hour for an hour window. A day window may not exceed 365 days and an hour window 720 hours. ' schema: type: string minLength: 1 pattern: ^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2}))?$ example: '2026-05-25' - $ref: '#/components/parameters/StatsTimezone' - name: compare in: query required: false description: 'Set to `previous_period` to include the received-message count for the immediately preceding window of equal length. The response also includes the change between the two, so you can show "+X% vs last period" without a second request. ' schema: $ref: '#/components/schemas/StatsComparePeriod' responses: '200': description: Total received messages for the requested period. content: application/json: schema: $ref: '#/components/schemas/WhatsAppInboundStatsSummaryResponse' '400': $ref: '#/components/responses/BadRequest' '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' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk /v1/whatsapp/stats/inbound/daily: get: operationId: getWhatsAppInboundStatsDaily x-snippet-key: whatsapp.stats.inbound.daily summary: Get daily inbound WhatsApp statistics description: 'Returns the number of WhatsApp messages your business numbers received, one row per calendar day. Rows use the time each message reached your number, and days with no messages contain a zero count. Each row contains only a count because a received message has one state. Use the send statistics endpoints for lifecycle and delivery-latency data about messages you send. `from` and `to` are optional calendar days (YYYY-MM-DD), defaulting to the trailing 30 days. The maximum window is 365 days; requesting a longer range returns 422. Set `timezone` to bucket rows by your local calendar day instead of UTC.' tags: - whatsapp-stats x-audiences: - public - command security: - BearerAuth: [] - CookieAuth: [] parameters: - name: from in: query required: false description: Start date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). The window may not exceed 365 days. Defaults to 30 days before `to` when omitted. schema: type: string format: date minLength: 1 example: '2026-05-01' - name: to in: query required: false description: End date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). The window may not exceed 365 days. Defaults to today in that timezone when omitted. schema: type: string format: date minLength: 1 example: '2026-05-25' - $ref: '#/components/parameters/StatsTimezone' responses: '200': description: Daily received-message counts for the requested period. content: application/json: schema: $ref: '#/components/schemas/WhatsAppInboundStatsResponse' '400': $ref: '#/components/responses/BadRequest' '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' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk /v1/whatsapp/stats/inbound/hourly: get: operationId: getWhatsAppInboundStatsHourly x-snippet-key: whatsapp.stats.inbound.hourly summary: Get hourly inbound WhatsApp statistics description: 'Returns the number of WhatsApp messages your business numbers received, one row per hour. Rows use the time each message reached your number, in UTC by default or local time when you set `timezone`. Hours with no messages contain a zero count. Each row contains only a count because a received message has one state. Use the send statistics endpoints for lifecycle and delivery-latency data about messages you send. `from` and `to` are optional RFC 3339 instants defaulting to the trailing 168 hours, rounded down to the enclosing hour and echoed back in `period`, both bounds inclusive. A single request may span at most 30 days (720 hourly rows); for longer ranges use the daily endpoint. Requesting an hourly window longer than 30 days, or a `from` after `to`, returns 422.' tags: - whatsapp-stats x-audiences: - public - command security: - BearerAuth: [] - CookieAuth: [] parameters: - name: from in: query required: false description: Start of the window (RFC 3339 instant). Rounded down to the start of its hour (the local hour when `timezone` is set, otherwise the UTC hour), and that hour is included. The window may not exceed 30 days (720 hours). Defaults to 168 hours (7 days) before `to` when omitted. schema: type: string format: date-time minLength: 1 example: '2026-05-25T00:00:00Z' - name: to in: query required: false description: End of the window (RFC 3339 instant). Rounded down to the start of its hour (the local hour when `timezone` is set, otherwise the UTC hour), and that hour is included (both bounds inclusive). The window may not exceed 30 days (720 hours). Defaults to the current hour when omitted. schema: type: string format: date-time minLength: 1 example: '2026-05-25T23:59:59Z' - $ref: '#/components/parameters/StatsTimezone' responses: '200': description: Hourly received-message counts for the requested period. content: application/json: schema: $ref: '#/components/schemas/WhatsAppInboundStatsResponse' '400': $ref: '#/components/responses/BadRequest' '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' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk /v1/whatsapp/stats/inbound/phone-numbers: get: operationId: getWhatsAppInboundStatsByPhoneNumber x-snippet-key: whatsapp.stats.inbound.by_phone_number summary: Get inbound WhatsApp statistics by phone number description: 'Returns how many WhatsApp messages each of your business phone numbers received. Rows are ranked by volume descending and capped at the requested `limit` (default 50, max 200), counted by the time each message reached the number. Each row contains only a count because a received message has one state. Lifecycle and delivery-latency data do not apply to received messages. The maximum window is 365 days; a longer range returns 422. Set `timezone` to resolve the period against your local calendar instead of UTC.' tags: - whatsapp-stats x-audiences: - public - command security: - BearerAuth: [] - CookieAuth: [] parameters: - name: from in: query required: false description: Inclusive start of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Must not be after `to`. Max window 365 days. Defaults to 30 days before `to` when omitted. schema: type: string format: date minLength: 1 example: '2026-06-14' - name: to in: query required: false description: Inclusive end of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Max window 365 days. Defaults to today in that timezone when omitted. schema: type: string format: date minLength: 1 example: '2026-07-13' - $ref: '#/components/parameters/StatsTimezone' - name: limit in: query required: false description: Maximum number of phone-number rows to return, ranked by received-message volume descending. schema: type: integer minimum: 1 maximum: 200 default: 50 responses: '200': description: Per-phone-number breakdown of received messages for the requested period. content: application/json: schema: $ref: '#/components/schemas/WhatsAppInboundStatsByPhoneNumberResponse' '400': $ref: '#/components/responses/BadRequest' '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' '503': $ref: '#/components/responses/ServiceUnavailable' x-surfaces: - cli - make - mcp - sdk components: schemas: WhatsAppInboundStatsPoint: type: object additionalProperties: false readOnly: true description: 'Received-message count for one time bucket (a calendar day or hour), bucketed by the time each message reached your number. ' required: - bucket - received properties: bucket: type: string minLength: 1 pattern: ^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2}))?$ readOnly: true description: The day (YYYY-MM-DD) or hour (RFC 3339, on the hour) this point covers, matching the request's grain. example: '2026-05-25' received: type: integer minimum: 0 readOnly: true description: Distinct messages received in this bucket. example: 182 WhatsAppStatsSeriesPeriod: type: object additionalProperties: false description: 'The window and bucket grain the response covers, echoed from the request, plus the freshness boundary the data is current to. ' required: - from - to - grain properties: from: type: string minLength: 1 pattern: ^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2}))?$ readOnly: true description: Inclusive start of the window. A calendar day (YYYY-MM-DD) on the day grain, an RFC 3339 instant on the hour grain. example: '2026-05-01' to: type: string minLength: 1 pattern: ^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2}))?$ readOnly: true description: Inclusive end of the window. A calendar day (YYYY-MM-DD) on the day grain, an RFC 3339 instant on the hour grain. example: '2026-05-25' grain: $ref: '#/components/schemas/StatsGrain' readOnly: true data_as_of: type: - string - 'null' format: date-time readOnly: true description: 'Latest time reflected in the statistics. More recent events might not be included yet. Null when the freshness boundary is unavailable. ' example: '2026-05-25T14:03:10Z' WhatsAppErrorCode: type: string minLength: 1 x-extensible-enum: - insufficient_balance - price_not_found - internal_error - undeliverable - service_window_expired - rate_limited - recipient_suppressed - media_rejected description: 'Standardized failure reason: - `insufficient_balance`: The workspace wallet could not fund the send. - `price_not_found`: No price was configured for the destination and template. - `internal_error`: An unexpected service failure occurred. - `undeliverable`: The recipient could not be reached. - `service_window_expired`: The 24-hour service window closed; send a template. - `rate_limited`: The send was throttled. - `recipient_suppressed`: The recipient is on the workspace suppression list. - `media_rejected`: WhatsApp could not fetch the media URL, or refused the file it found there; `description` carries its reason. This is an open enum. Accept unrecognized values. ' 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' WhatsAppStatsByTemplateCategoryResponse: type: object additionalProperties: false description: Per-template-category breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200). required: - period - data - total properties: period: $ref: '#/components/schemas/WhatsAppStatsSummaryPeriod' description: The window the response covers (echoed back), plus `data_as_of`. data: type: array readOnly: true description: Category rows ranked by accepted volume descending. items: $ref: '#/components/schemas/WhatsAppTemplateCategoryStatsPoint' total: type: integer minimum: 0 readOnly: true description: Total distinct categories with activity in the period, regardless of `limit`. example: 4 WhatsAppLatencyQuantiles: type: object additionalProperties: false readOnly: true description: 'Approximate p50, p95, and p99 latency percentiles in milliseconds for one latency family. All three are null when no qualifying event contributed a measurement. ' required: - p50_ms - p95_ms - p99_ms properties: p50_ms: type: - integer - 'null' minimum: 0 readOnly: true description: Median (50th percentile) latency in milliseconds. Null when no qualifying event contributed a measurement. example: 610 p95_ms: type: - integer - 'null' minimum: 0 readOnly: true description: 95th percentile latency in milliseconds. Null when no qualifying event contributed a measurement. example: 2140 p99_ms: type: - integer - 'null' minimum: 0 readOnly: true description: 99th percentile latency in milliseconds. Null when no qualifying event contributed a measurement. example: 5380 WhatsAppTemplateCategory: type: string minLength: 1 x-extensible-enum: - authentication - utility - marketing description: 'Meta''s content classification for a template. - `authentication`: delivers one-time passcodes. - `utility`: delivers transaction-triggered updates (receipts, order status). - `marketing`: carries promotional content. The category determines the sender number and price. This is an open enum. Accept unrecognized values. ' WhatsAppStatsByPhoneNumberResponse: type: object additionalProperties: false description: Per-phone-number breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200). required: - period - data - total properties: period: $ref: '#/components/schemas/WhatsAppStatsSummaryPeriod' description: The window the response covers (echoed back), plus `data_as_of`. data: type: array readOnly: true description: Phone-number rows ranked by accepted volume descending. items: $ref: '#/components/schemas/WhatsAppPhoneNumberStatsPoint' total: type: integer minimum: 0 readOnly: true description: Total distinct phone numbers with activity in the period, regardless of `limit`. example: 2 WhatsAppCountryStatsPoint: type: object additionalProperties: false description: Lifecycle counts, derived rates, engagement and latency for a single destination country over the requested period. required: - country - delivery - engagement - latency properties: country: readOnly: true description: The destination country this row aggregates, as an ISO 3166-1 alpha-2 code. `ZZ` collects recipients whose country could not be resolved. allOf: - $ref: '#/components/schemas/CountryCode' delivery: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppDeliveryStats' engagement: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppEngagementStats' latency: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppLatencyStats' StatsComparePeriod: type: string description: 'Set to `previous_period` to also return the same figures for the immediately preceding window of equal length, plus the change between the two, so you can show "+X% vs last period" without a second request. ' enum: - previous_period WhatsAppStatsSummaryPeriod: type: object additionalProperties: false description: 'The window the server actually computed against. The summary serves two window grains: calendar days (bounds are YYYY-MM-DD) and hours (bounds are RFC 3339 instants on the hour). The grain of `from` and `to` mirrors the grain of the request''s bounds. ' required: - from - to properties: from: type: string minLength: 1 pattern: ^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2}))?$ readOnly: true description: Inclusive start of the window, as a calendar day (`YYYY-MM-DD`) or an RFC 3339 hour boundary. example: '2026-05-01' to: type: string minLength: 1 pattern: ^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2}))?$ readOnly: true description: Inclusive end of the window, as a calendar day (`YYYY-MM-DD`) or an RFC 3339 hour boundary. example: '2026-05-25' data_as_of: type: - string - 'null' format: date-time readOnly: true description: 'Latest time reflected in the statistics. More recent events might not be included yet. Null when the freshness boundary is unavailable. ' example: '2026-05-25T14:03:10Z' WhatsAppStatsResponse: type: object additionalProperties: false description: 'Time-series stats payload. `period` echoes the range and bucket grain the server computed against; `data` is one row per bucket in chronological order. ' required: - period - data properties: period: $ref: '#/components/schemas/WhatsAppStatsSeriesPeriod' data: type: array readOnly: true description: One row per day or hour in chronological order. Buckets with no activity contain zero counts. items: $ref: '#/components/schemas/WhatsAppStatsPoint' WhatsAppStatsComparisonDelta: type: object additionalProperties: false readOnly: true description: 'Changes from the previous period. A `*_pct_change` value is the signed relative change `(current - previous) / previous` and is null when the previous count is zero. A `*_rate_pp` value is the signed difference between rate fractions and is null when either rate is undefined. ' required: - accepted_pct_change - sent_pct_change - delivered_pct_change - failed_pct_change - rejected_pct_change - read_pct_change - delivery_rate_pp - failure_rate_pp - read_rate_pp properties: accepted_pct_change: type: - number - 'null' readOnly: true description: Relative change in accepted messages (`delivery.accepted`) versus the previous period, as a signed fraction. Null when the previous period accepted none. example: 0.508 sent_pct_change: type: - number - 'null' readOnly: true description: Relative change in sent messages (`delivery.sent`) versus the previous period, as a signed fraction. Null when the previous period had none. example: 0.508 delivered_pct_change: type: - number - 'null' readOnly: true description: Relative change in delivered messages (`delivery.delivered`) versus the previous period, as a signed fraction. Null when the previous period delivered none. example: 0.513 failed_pct_change: type: - number - 'null' readOnly: true description: Relative change in failed messages (`delivery.failed`) versus the previous period, as a signed fraction. Null when the previous period had none. example: -0.194 rejected_pct_change: type: - number - 'null' readOnly: true description: Relative change in rejected messages (`delivery.rejected`) versus the previous period, as a signed fraction. Null when the previous period had none. example: 0.084 read_pct_change: type: - number - 'null' readOnly: true description: Relative change in messages read (`engagement.read`) versus the previous period, as a signed fraction. Null when the previous period had none. example: 0.568 delivery_rate_pp: type: - number - 'null' minimum: -1 maximum: 1 readOnly: true description: Signed difference between this period's and the previous period's delivery rate, both fractions in [0,1] (multiply by 100 for percentage points). Null when either period's delivery rate is undefined. example: 0.0031 failure_rate_pp: type: - number - 'null' minimum: -1 maximum: 1 readOnly: true description: Signed difference between this period's and the previous period's failure rate, both fractions in [0,1] (multiply by 100 for percentage points). Null when either period's failure rate is undefined. example: -0.0045 read_rate_pp: type: - number - 'null' readOnly: true description: 'Signed difference between the current and previous read-rate fractions. Multiply by 100 for percentage points. The value can fall outside `[-1, 1]` because a read receipt can arrive for a message whose delivery receipt did not, and high-volume counts are approximate. Null when either rate is undefined. ' example: 0.0232 WhatsAppStatsSummary: type: object additionalProperties: false description: 'WhatsApp lifecycle counts, rates, engagement, and latency percentiles for the full requested period. Counts aggregate the time buckets. Latency percentiles cover the whole period. Rates are null when their denominator is zero. ' required: - period - delivery - engagement - latency properties: period: $ref: '#/components/schemas/WhatsAppStatsSummaryPeriod' description: The window the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. delivery: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppDeliveryStats' engagement: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppEngagementStats' latency: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppLatencyStats' comparison: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppStatsComparison' WhatsAppTemplateID: type: string minLength: 1 pattern: ^wat_[0-9a-hjkmnp-tv-z]{26}$ example: wat_01krdgeqcxet5s7t44vh8rt9mg WhatsAppInboundPhoneNumberStatsPoint: type: object additionalProperties: false readOnly: true description: Received-message count for a single business phone number over the requested period. required: - phone_number - received properties: phone_number: type: string minLength: 1 readOnly: true description: The business phone number that received the messages, in E.164 form. example: '+13124495569' received: type: integer minimum: 0 readOnly: true description: Distinct messages the number received in the period. example: 182 WhatsAppStatsByCountryResponse: type: object additionalProperties: false description: 'Per-country breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200). ' required: - period - data - total properties: period: $ref: '#/components/schemas/WhatsAppStatsSummaryPeriod' readOnly: true description: The window the response covers (echoed back), plus `data_as_of`. data: type: array readOnly: true description: 'Country rows ranked by accepted volume descending. Empty when no eligible activity occurred in the period; rows sum to the summary less group-send volume, and less any pre-cutover phone-addressed sends still inside the window. ' items: $ref: '#/components/schemas/WhatsAppCountryStatsPoint' total: type: integer minimum: 0 readOnly: true description: Total distinct countries with activity in the period, regardless of `limit`. example: 4 WhatsAppInboundStatsComparison: type: object additionalProperties: false readOnly: true description: 'The received-message count for the equal-length, inclusive period ending immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request. ' required: - period - received - delta properties: period: $ref: '#/components/schemas/WhatsAppStatsSummaryPeriod' description: The preceding window these comparison figures cover, the equal-length window ending immediately before the requested start (the prior day for day windows, the prior hour for hour windows). For a request covering 2026-05-01 to 2026-05-25, this is 2026-04-06 to 2026-04-30, both inclusive. example: from: '2026-04-06' to: '2026-04-30' data_as_of: '2026-05-25T14:03:10Z' received: type: integer minimum: 0 readOnly: true description: Distinct messages received in the preceding period. example: 3980 delta: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppInboundStatsComparisonDelta' WhatsAppErrorCodeStatsPoint: type: object additionalProperties: false description: Number of failed messages for a single normalized failure reason over the requested period. required: - error_code - count properties: error_code: readOnly: true description: The normalized failure reason this row aggregates, matching the `last_error.code` reported on an individual failed message. allOf: - $ref: '#/components/schemas/WhatsAppErrorCode' count: type: integer minimum: 0 readOnly: true description: Distinct messages that failed with this reason in scope. example: 18 WhatsAppTagStatsPoint: type: object additionalProperties: false description: Lifecycle counts, derived rates, and engagement for a single tag (name:value) over the requested period. required: - tag - delivery - engagement - latency properties: tag: type: string minLength: 1 readOnly: true description: The tag this row aggregates, in `name:value` form. example: campaign:summer_sale delivery: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppDeliveryStats' engagement: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppEngagementStats' latency: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppLatencyStats' CountryCode: type: string minLength: 2 maxLength: 2 pattern: ^[A-Za-z]{2}$ description: ISO 3166-1 alpha-2 country code. example: US WhatsAppStatsByTemplateResponse: type: object additionalProperties: false description: Per-template breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200). required: - period - data - total properties: period: $ref: '#/components/schemas/WhatsAppStatsSummaryPeriod' description: The window the response covers (echoed back), plus `data_as_of`. data: type: array readOnly: true description: Template rows ranked by accepted volume descending. items: $ref: '#/components/schemas/WhatsAppTemplateStatsPoint' total: type: integer minimum: 0 readOnly: true description: Total distinct templates with activity in the period, regardless of `limit`. example: 7 WhatsAppEngagementStats: type: object additionalProperties: false readOnly: true description: 'WhatsApp engagement counts and the derived read rate for the scope of the containing row (the whole requested period or a breakdown dimension). The `read` field is the number of distinct messages confirmed read by the recipient. Send time determines attribution; the instant the read receipt arrived does not. A read is counted in the period its message was accepted in, alongside that message''s own delivery when one arrived. The read rate divides reads by messages delivered in the same scope and is null when its denominator is zero. Very large counts are close estimates rather than exact tallies. ' required: - read - read_rate properties: read: type: integer minimum: 0 readOnly: true description: Distinct messages confirmed read by the recipient. example: 3105 read_rate: type: - number - 'null' minimum: 0 readOnly: true description: 'Distinct messages read relative to messages delivered in the same scope, computed as `read / delivery.delivered`. Both counts are attributed by send time, so a read is counted alongside its own message''s delivery. The rate can exceed 1 where a read receipt arrived for a message whose delivery receipt did not, or, at high volume, because the counts are close estimates. Null when `delivery.delivered` is zero. ' example: 0.6578 WhatsAppInboundStatsComparisonDelta: type: object additionalProperties: false readOnly: true description: 'The change from the preceding period to the requested one. The `received_pct_change` field is a signed relative change, computed as `(current - previous) / previous`. A value of `0.5` means 50% higher, and `-0.2` means 20% lower. The field is null when the previous period received none. ' required: - received_pct_change properties: received_pct_change: type: - number - 'null' readOnly: true description: Relative change in received messages versus the previous period, as a signed fraction. Null when the previous period received none. example: 0.058 WhatsAppDeliveryCounts: type: object additionalProperties: false readOnly: true description: 'WhatsApp lifecycle counts for a time bucket, attributed by send time. A message accepted on Monday and delivered on Tuesday counts in Monday''s bucket. The sibling `engagement` block reports read counts. Rates are available only for the whole period. Very large counts are close estimates rather than exact tallies. ' required: - accepted - sent - delivered - failed - rejected properties: accepted: type: integer minimum: 0 readOnly: true description: Distinct messages accepted for sending after admission checks. example: 4820 sent: type: integer minimum: 0 readOnly: true description: Distinct messages handed off for delivery. example: 4810 delivered: type: integer minimum: 0 readOnly: true description: Distinct messages confirmed delivered to the recipient's device. example: 4720 failed: type: integer minimum: 0 readOnly: true description: Distinct messages that failed during sending or delivery. example: 25 rejected: type: integer minimum: 0 readOnly: true description: Distinct messages rejected before any send attempt, because the recipient is on the workspace's suppression list, no reachable recipient was given, the destination has no price, or the wallet could not fund the send. Rejected messages are never charged and are not counted in `accepted`, so the total addressed is `accepted + rejected`. Excluded from `failure_rate`, which covers send failures only. example: 412 WhatsAppStatsByTagResponse: type: object additionalProperties: false description: Per-tag breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200). required: - period - data - total properties: period: $ref: '#/components/schemas/WhatsAppStatsSummaryPeriod' description: The window the response covers (echoed back), plus `data_as_of`. data: type: array readOnly: true description: Tag rows ranked by accepted volume descending. items: $ref: '#/components/schemas/WhatsAppTagStatsPoint' total: type: integer minimum: 0 readOnly: true description: Total distinct tags with activity in the period, regardless of `limit`. example: 12 WhatsAppStatsComparison: type: object additionalProperties: false readOnly: true description: 'The same statistics for the equal-length, inclusive period ending immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request. ' required: - period - delivery - engagement - latency - delta properties: period: $ref: '#/components/schemas/WhatsAppStatsSummaryPeriod' description: The preceding window these comparison figures cover, the equal-length window ending immediately before the requested start (the prior day for day windows, the prior hour for hour windows). For a request covering 2026-05-01 to 2026-05-25, this is 2026-04-06 to 2026-04-30, both inclusive. example: from: '2026-04-06' to: '2026-04-30' data_as_of: '2026-05-25T14:03:10Z' delivery: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppDeliveryStats' example: accepted: 3196 sent: 3190 delivered: 3120 failed: 31 rejected: 380 delivery_rate: 0.9762 failure_rate: 0.0097 engagement: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppEngagementStats' example: read: 1980 read_rate: 0.6346 latency: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppLatencyStats' delta: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppStatsComparisonDelta' WhatsAppInboundStatsSummaryResponse: type: object additionalProperties: false description: 'Total received messages for the requested period. ' required: - period - received properties: period: $ref: '#/components/schemas/WhatsAppStatsSummaryPeriod' description: The window the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. received: type: integer minimum: 0 readOnly: true description: Distinct messages received in the period, counted by the time each message reached your number. Computed across the whole window rather than summed from the daily or hourly series, so it can sit slightly below the sum of those rows. example: 4210 comparison: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppInboundStatsComparison' WhatsAppTemplateCategoryStatsPoint: type: object additionalProperties: false description: Lifecycle counts, derived rates, and engagement for a single WhatsApp template category over the requested period. required: - category - delivery - engagement - latency properties: category: readOnly: true description: The template category this row aggregates. allOf: - $ref: '#/components/schemas/WhatsAppTemplateCategory' delivery: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppDeliveryStats' engagement: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppEngagementStats' latency: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppLatencyStats' WhatsAppPhoneNumberStatsPoint: type: object additionalProperties: false description: Lifecycle counts, rates, and engagement for one business phone number over the requested period, including whether the number is shared. required: - phone_number - shared - delivery - engagement - latency properties: phone_number: type: string minLength: 1 readOnly: true description: The business sender phone number in E.164 form. example: '+13124495569' shared: type: boolean readOnly: true description: '`true` for a shared Bird-managed number; `false` for a number owned by your workspace. ' example: true delivery: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppDeliveryStats' engagement: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppEngagementStats' latency: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppLatencyStats' 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. WhatsAppEngagementCounts: type: object additionalProperties: false readOnly: true description: 'WhatsApp engagement counts for a time bucket, attributed by send time. A message accepted on Monday and read on Tuesday counts in Monday''s bucket. Read rates are available only for the whole period. Very large counts are close estimates rather than exact tallies. ' required: - read properties: read: type: integer minimum: 0 readOnly: true description: Distinct messages confirmed read by the recipient. example: 3105 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. ' Error: type: object additionalProperties: false required: - error properties: error: $ref: '#/components/schemas/ErrorBody' WhatsAppDeliveryStats: type: object additionalProperties: false readOnly: true description: 'WhatsApp lifecycle counts and rates for the requested period, attributed by send time, so a later delivery stays attributed to the period in which its message was accepted, and a recent period under-reports `delivered` while delivery reports are still arriving. The sibling `engagement` block reports read counts and rates. Rates are null when their denominator is zero. Very large counts are close estimates rather than exact tallies. ' required: - accepted - sent - delivered - failed - rejected - delivery_rate - failure_rate properties: accepted: type: integer minimum: 0 readOnly: true description: Distinct messages accepted for sending after admission checks. This is the denominator for `delivery_rate` and `failure_rate`. example: 4820 sent: type: integer minimum: 0 readOnly: true description: Distinct messages handed off for delivery. example: 4810 delivered: type: integer minimum: 0 readOnly: true description: Distinct messages confirmed delivered to the recipient's device. example: 4720 failed: type: integer minimum: 0 readOnly: true description: Distinct messages that failed during sending or delivery. example: 25 rejected: type: integer minimum: 0 readOnly: true description: Distinct messages rejected before any send attempt, because the recipient is on the workspace's suppression list, no reachable recipient was given, the destination has no price, or the wallet could not fund the send. Rejected messages are never charged and are not counted in `accepted`, so the total addressed is `accepted + rejected`. Excluded from `failure_rate`, which covers send failures only. example: 412 delivery_rate: type: - number - 'null' minimum: 0 maximum: 1 readOnly: true description: 'Share of accepted messages that were delivered, computed as `delivered / accepted`. Null when no messages were accepted in scope. ' example: 0.9793 failure_rate: type: - number - 'null' minimum: 0 maximum: 1 readOnly: true description: 'Share of accepted messages that ultimately failed, computed as `failed / accepted`. Null when no messages were accepted in scope. ' example: 0.0052 WhatsAppStatsByErrorCodeResponse: type: object additionalProperties: false description: Per-error-code failure breakdown for the requested period, ranked by failure count descending and capped at the requested `limit` (default 50, max 200). required: - period - data - total properties: period: $ref: '#/components/schemas/WhatsAppStatsSummaryPeriod' description: The window the response covers (echoed back), plus `data_as_of`. data: type: array readOnly: true description: Error-code rows ranked by failure count descending. Empty when no failures occurred in the period. items: $ref: '#/components/schemas/WhatsAppErrorCodeStatsPoint' total: type: integer minimum: 0 readOnly: true description: Total distinct error codes with failures in the period, regardless of `limit`. example: 3 WhatsAppInboundStatsByPhoneNumberResponse: type: object additionalProperties: false description: Per-phone-number breakdown of received messages for the requested period, ranked by volume descending and capped at the requested `limit` (default 50, max 200). required: - period - data - total properties: period: $ref: '#/components/schemas/WhatsAppStatsSummaryPeriod' description: The window the response covers (echoed back), plus `data_as_of`. data: type: array readOnly: true description: 'Phone-number rows ranked by received-message volume descending, capped at the requested `limit`. A number with no messages in the period is absent rather than zero-filled, because unlike a time bucket it is not part of a continuous axis. ' items: $ref: '#/components/schemas/WhatsAppInboundPhoneNumberStatsPoint' total: type: integer minimum: 0 readOnly: true description: Total distinct phone numbers with received messages in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more. example: 2 StatsGrain: type: string minLength: 1 enum: - day - hour readOnly: true description: The bucket grain of the series, either `day` or `hour`. example: day WhatsAppInboundStatsResponse: type: object additionalProperties: false description: 'Received-message time series. `period` echoes the range the server computed against; `data` is one row per bucket in chronological order. ' required: - period - data properties: period: $ref: '#/components/schemas/WhatsAppStatsSeriesPeriod' data: type: array readOnly: true description: One row per bucket (day or hour, matching the request) in the period, in chronological order. Buckets with no activity are included with a count of zero, so the series charts continuously without client-side gap handling. items: $ref: '#/components/schemas/WhatsAppInboundStatsPoint' WhatsAppStatsPoint: type: object additionalProperties: false readOnly: true description: 'WhatsApp lifecycle counts, engagement, and latency percentiles for one time bucket (a calendar day or hour), bucketed by send time. Every count in a bucket describes the messages accepted in it, regardless of when their later events arrived. Rates apply to the whole window rather than individual buckets. ' required: - bucket - delivery - engagement - latency properties: bucket: type: string minLength: 1 readOnly: true description: The day (YYYY-MM-DD) or hour (RFC 3339, on the hour) this point covers, matching the period's grain. example: '2026-05-25' delivery: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppDeliveryCounts' engagement: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppEngagementCounts' latency: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppLatencyStats' WhatsAppTemplateStatsPoint: type: object additionalProperties: false description: Lifecycle counts, derived rates, engagement and latency for a single WhatsApp template over the requested period. required: - template_id - delivery - engagement - latency properties: template_id: allOf: - $ref: '#/components/schemas/WhatsAppTemplateID' readOnly: true description: 'The template these messages were sent from, using the same `id` the WhatsApp template endpoints return. A send that resolved no template does not appear in this breakdown. A template renamed after it was used to send still reports under this one `id`, and a template deleted after sending keeps its row rather than dropping the messages. ' delivery: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppDeliveryStats' engagement: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppEngagementStats' latency: readOnly: true allOf: - $ref: '#/components/schemas/WhatsAppLatencyStats' WhatsAppLatencyStats: type: object additionalProperties: false readOnly: true description: 'Latency percentiles in milliseconds for the requested scope: - `processing`: From acceptance to WhatsApp handoff. - `delivery`: From WhatsApp handoff to delivery confirmation. - `total`: From acceptance to delivery confirmation. Each family is omitted when no qualifying message contributes a measurement. Individual percentiles can also be null. `delivery` is measured on a best-effort basis, so it can be absent for a scope whose `processing` and `total` are present. ' example: processing: p50_ms: 610 p95_ms: 2140 p99_ms: 5380 delivery: p50_ms: 1530 p95_ms: 6820 p99_ms: 18400 total: p50_ms: 2180 p95_ms: 9060 p99_ms: 24300 properties: processing: $ref: '#/components/schemas/WhatsAppLatencyQuantiles' delivery: $ref: '#/components/schemas/WhatsAppLatencyQuantiles' total: $ref: '#/components/schemas/WhatsAppLatencyQuantiles' parameters: StatsTimezone: name: timezone in: query required: false description: 'IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. ' schema: type: string minLength: 1 example: Asia/Kathmandu WhatsAppStatsTemplateFilter: name: template in: query required: false description: 'Restricts the statistics to one template, identified by its ID (`wat_…`) or slug. Mutually exclusive with the other dimension filters (`category`, `phone_number`, `tag`); only one may be set per request. An ID matches the `template_id` key on a row of the per-template breakdown; a slug is accepted for callers that predate that key and resolves to the same messages. ' schema: type: string minLength: 1 maxLength: 63 pattern: ^(wat_[0-9a-hjkmnp-tv-z]{26}|[a-z0-9]([a-z0-9_-]*[a-z0-9])?)$ example: wat_01krdgeqcxet5s7t44vh8rt9mg responses: InternalError: description: Internal server error 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' 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' 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 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' 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. '