Email stats API
The email stats API returns the aggregates shown on the Metrics dashboard, plus the sending-health verdict computed from them. 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, PHP, and Go SDKs under email.stats and email.health, the bird CLI exposes them as bird email stats and bird email health, and an agent reaches them through the email_stats_* and email_health 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.
Sending health
GET /v1/email/health answers the question the aggregates leave to you: whether your sending is heading for trouble. It returns one verdict for the window plus signals for delivery, opens, bounces, and complaints. Each signal carries its rate and verdict. Delivery, bounce, and complaint signals also carry the boundaries that set their verdicts; open rate has no risk boundaries. That is what lets a status badge follow our bands without a copy of them compiled into your own client.
Code example
{
"period": {
"data_as_of": null,
"from": "2026-05-25",
"to": "2026-06-01"
},
"status": "watching",
"signals": [
{
"metric": "delivery_rate",
"value": 0.995,
"limit": null,
"status": "healthy",
"thresholds": {
"direction": "below",
"throttled": 0.984,
"watching": 0.99
}
},
{
"metric": "open_rate",
"value": 0.20100503,
"limit": null,
"status": "healthy"
},
{
"metric": "bounce_rate",
"value": 0.005,
"limit": 0.005,
"status": "watching",
"thresholds": {
"direction": "above",
"throttled": 0.006,
"watching": 0.004
}
},
{
"metric": "complaint_rate",
"value": 0.00010050251,
"limit": 0.003,
"status": "healthy",
"thresholds": {
"direction": "above",
"throttled": 0.001,
"watching": 0.0006
}
}
]
}Match each signal on its metric. The top-level status is the worst of the delivery, bounce, and complaint verdicts: healthy, watching, or throttled. open_rate sits outside that roll-up, because a high open rate is never a risk, and it is the only signal that can read strong.
Two fields on a signal are easy to confuse. limit is the reference deliverability limit for the rate, and it is null on the rates that have none. thresholds is where the verdict itself changes: watching and throttled are the two boundaries, and direction names the risky side of them, above for bounce and complaint rates and below for delivery rate. The boundaries are exclusive, so a rate sitting exactly on one keeps the better status.
Because the boundaries come back on the wire, you can grade slices this endpoint does not compute: rank your senders with /sending-domains, then classify each row against the bounce-rate boundaries the health response returned. The Metrics dashboard uses separate warning bands for hard-bounce and complaint rates. This API evaluates aggregate bounce, complaint, and delivery rates, so its verdict can differ from a dashboard warning.
A throttled verdict reports deliverability risk. It does not pause your sending.
The window works differently from the endpoints above. from and to are calendar days in UTC, there is no timezone parameter, and no dimension filter applies. Omit both and the window ends today and starts 7 days earlier. The maximum is 365 days.
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
- Sending health reference: the verdict, its per-rate signals, and their boundaries
- Events and webhooks: the per-recipient stream when an aggregate is not enough
Related resources
Continue with the documentation, guides and examples for this topic. Resources are in English.
Watch the guideGetting started with emailExplore the capabilityEmailFollow the learning pathBuild your first integrationImplementation guideSend your first email
Try the practice and get an implementation brief