Email stats API
The email stats API returns the aggregates shown on the Metrics dashboard. Use it to build dashboards, export data, or monitor email health. They require an API key with read access to the emails scope.
Typed methods ship in the TypeScript, Python, and Go SDKs under email.stats, the bird CLI exposes them as bird email stats, and an agent reaches them through the email_stats_* MCP tools. Full request and response schemas are in the API reference.
The aggregate and the time series
Three endpoints cover the top of the dashboard:
- GET /v1/email/stats/summary returns one aggregate row for the whole window. It includes lifecycle counts for accepted, delivered, bounced, complained, opened, clicked, and their sub-types. It also includes the derived delivery_rate, bounce_rate, complaint_rate, open_rate, and click_rate. Processing, delivery, and total latency percentiles cover p50, p95, and p99. Pass compare=previous_period and the response also includes the preceding equal-length window and the change from it.
- GET /v1/email/stats/daily and GET /v1/email/stats/hourly return the same counts one row per day or per hour, gap-filled with zero rows so a chart never has holes in it.
Every rate comes back as a fraction between 0 and 1, so a delivery_rate of 0.9939 is 99.39%. A rate whose denominator is zero is null, which is how a period that delivered nothing reports open_rate rather than reading 0. Rates use event time for attribution. Send time does not affect which window includes an event, so engagement arriving during the window for an older message is included. The exact formula behind each one, including how a late out-of-band bounce moves a recipient out of the delivered count, is documented per field on the summary reference.
Every response echoes the window it computed against, plus data_as_of: the instant the figures are current to. The aggregation refreshes every few seconds, so a response is near-real-time rather than live. Label your own dashboard with data_as_of rather than presenting the numbers as being to the second.
Choosing the window
from and to take either a calendar day (YYYY-MM-DD) or an RFC 3339 instant, and which forms an endpoint accepts differs:
| Endpoint | Bounds | Maximum window |
|---|---|---|
| /summary | Both days, or both instants | 365 days, or 720 hours on instants |
| /daily | Calendar days | 365 days |
| /hourly | RFC 3339 instants | 720 hours (30 days) |
Instant bounds are hour-grain, which is what makes a rolling "last 24 hours" a single request. On /summary, mixing a day with an instant returns a 422.
Set timezone to an IANA identifier such as America/New_York to have day and hour boundaries, and the defaults used when you omit from and to, computed in that zone instead of UTC. While timezone is set, from and to must not include a UTC offset of their own.
Breakdowns
The 13 breakdown endpoints slice the same delivery and engagement numbers by one dimension:
- Senders: /sending-domains, /sending-ips, and /recipient-domains (the mailbox domain you sent to).
- Where it landed: /mailbox-providers (Gmail, Outlook, and so on) and /mailbox-provider-regions.
- What you sent: /tags (the tags you set at send time, the most flexible cut), /categories, /templates, and /broadcasts.
- Engagement context: /locations (recipient geography) and /clients (the mail client that rendered the open).
- Failures: /bounce-codes (grouped by the receiving server's response) and /complaint-types.
All of them live under /v1/email/stats/. Rows come back ranked descending by a sort metric and capped at limit (default 50, maximum 200). The response also includes total, the number of distinct dimension values in the window. Compare total with the returned row count to identify a capped result. Rows whose sort metric is a rate with a zero denominator go last.
Each endpoint's sort default is the metric it exists to rank by:
| Default | Breakdowns |
|---|---|
| processed | /tags, /categories, /templates, /broadcasts, /sending-domains, /recipient-domains |
| delivered | /sending-ips, /mailbox-providers, /mailbox-provider-regions |
| unique_opens | /locations, /clients |
| bounced | /bounce-codes |
| complained | /complaint-types |
include_trend=true adds a per-bucket rate series to each row, ready for sparklines. It applies to the tag, category, template, sending-domain, sending-IP, recipient-domain, mailbox-provider, and mailbox-provider-region breakdowns.
The summary and time-series endpoints also take one dimension filter per request. Choose category, sending_domain, sending_ip, recipient_domain, tag, or template. The filter scopes an aggregate to one sender or campaign without switching to a breakdown. Passing more than one returns a 422.
Test traffic in the numbers
Sends to sandbox addresses run through the same aggregation, so test traffic appears in every endpoint here exactly as it does on the dashboard.
Next steps
- Email metrics: how the dashboard presents these numbers and when to act on them
- Stats summary reference: every field, filter, and rate formula
- Events and webhooks: the per-recipient stream when an aggregate is not enough