Documentation
Sign inGet started

Email stats API

Every number on the Metrics dashboard comes from the email stats API, and you can pull the same aggregates yourself: to build your own dashboard, feed a data warehouse, or wire a health check into your alerting. The endpoints are read-only, workspace-scoped, and need an API key with read access to the emails scope. They read the same rollups the dashboard does, so a value you fetch matches what the dashboard shows for the same range.
Typed methods ship in the TypeScript, Python, and Go SDKs under email.stats, and the same reads are available from the bird CLI (bird email stats …) and the MCP server. 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: the single-row aggregate behind the health cards, holding the lifecycle counts (accepted, delivered, bounced, complained, opened, clicked, and their sub-types), the derived delivery_rate, bounce_rate, complaint_rate, open_rate, and click_rate, and the processing, delivery, and total latency percentiles (p50/p95/p99) for the range. Pass compare=previous_period to also get the preceding equal-length window and the change between them, the "+X% vs last period" the cards show.
  • GET /v1/email/stats/daily and GET /v1/email/stats/hourly: one row of the same counts per day or per hour, for charting over time.
Rates attribute by event time, not send time, so engagement that arrives during the range for a message sent earlier is included. A rate is null when its denominator is zero: open_rate is null for a period that delivered nothing, rather than reading 0. The exact formula behind each rate, including how late out-of-band bounces adjust the delivered denominator, is documented per field on the summary reference.

Choosing the window

from and to accept either a calendar day (YYYY-MM-DD) or an RFC 3339 instant. With days the window covers whole calendar days (up to 365); with instants it is hour-grain (up to 720 hours, 30 days), so a rolling "last 24 hours" is a single request. Both bounds must use the same form; mixing them returns a 422. Set timezone to an IANA identifier (for example America/New_York) to have day and hour boundaries, and the defaults used when from/to are omitted, computed in that zone instead of UTC; sub-hour offsets like Asia/Kolkata (+05:30) are handled correctly.

Breakdowns

Thirteen breakdown endpoints slice the same delivery and engagement numbers by a single dimension, ranking rows by a sort metric (default processed) descending and capping the result at limit (default 50, maximum 200) with a total for "top N of M":
  • Senders: /sending-domains, /sending-ips, and /recipient-domains (the mailbox domain you sent to).
  • Where it landed: /mailbox-providers (Gmail, Outlook, …) 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 breakdowns live under /v1/email/stats/. Pass include_trend=true to add a per-bucket rate series to each row, ready for sparklines. The summary and time-series endpoints also take a single dimension filter per request (category, sending_domain, sending_ip, recipient_domain, tag, or template) so you can scope an aggregate to one sender or campaign without switching to a breakdown.

Test traffic in the numbers

Sends to sandbox addresses flow through the same rollups, so test runs show up in every endpoint here exactly as they do on the dashboard.

Next steps