nt_ ID or by an alias. Authenticate with a workspace as a bearer token.
Each bucket breaks the funnel out per provider and channel. When a message fails over between providers, the series shows which one errored and which one delivered.
Choosing the window
Ask for a window in one of two ways:- Relative:
lookback, an ISO 8601 duration counted back from now:?lookback=P90D,?lookback=P12W,?lookback=PT12H. - Absolute:
startandend, ISO 8601 timestamps:?start=2026-04-01T00:00:00Z&end=2026-05-01T00:00:00Z.
start and end are pair-or-nothing. Sending one without the other is a 400, as is a start that is not earlier than end.
Pick one form or the other. A request carrying both a lookback and an absolute pair is a 400, not a silent preference for one of them, because the two spell the same thing and honouring both would discard whichever lost. A request with none of the three defaults to lookback=P30D.
An end in the future is accepted and not clamped. Buckets past now come back empty.
Boundaries are snapped, and echoed back
Courier widens the window you asked for onto the granularity grid: the start is floored to its bucket, the end is ceiled to the next boundary. That way every bucket your window touches is returned whole rather than half-counted. The response echoes the snapped boundaries asstart and end. Chart against those, not against what you sent:
WEEK buckets start on Sunday, matching the analytics store Courier queries. A Monday-based week has to be assembled from DAY buckets.Granularity
granularity sets the bucket size: HOUR, DAY, WEEK, or MONTH. It defaults to DAY.
Fine granularities are capped by how much window they can cover, so a single response stays a reasonable size:
Both caps are measured on the snapped window, and each carries an allowance of a bucket at either edge. That is why a
P90D window at DAY passes even though snapping widens it past 90 days.
Asking for a finer granularity than the window allows is a 400. The fix is a coarser granularity or a shorter window, not a retry:
WEEK and MONTH have no window cap. No response carries more than 1000 buckets, and beyond that you get a 400 too.
Reading the response
series holds one entry per bucket, oldest first. Each entry carries the bucket’s start (period) and a data array with one row per provider and channel that handled a message in that bucket.
data for a bucket’s total, or filter them by channel to chart one channel on its own.
Rates are yours to compute from those sums:
sent of zero.
A few things worth knowing before you plot it:
- Every bucket in the window comes back, including the quiet ones, with
dataset to[]. The series is directly plottable, so you never reconstruct missing periods yourself. errorscounts provider attempts, not lost messages. A message that SendGrid rejected and Mailgun then delivered contributes toerrorson one row anddeliveredon another.undeliverableis the one that means the message never landed on that channel.openedandclickedare0on channels with no tracking, such as SMS without link tracking. A zero there means “not measured”, not “nobody read it”.- Sends without a template never appear. A message sent with inline content has no template to attribute, so it is counted nowhere in this endpoint.
- An unknown template id returns
200with an all-empty series, not a404. That is indistinguishable from a real template that has sent nothing. Validate template ids against if you need to tell the two apart.
Plan limits
Lookback is capped by plan, measured from the snappedstart:
On Enterprise the API itself imposes no lookback cap, so how far back you can reach is whatever the analytics store still holds. The console is narrower than the API here, because its custom-range calendar stops at January 1 of last year.
Reaching past the cap is a
402, not an empty result:
RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset, and a 429 adds Retry-After. If you are backfilling a dashboard, walk the windows sequentially rather than fanning out.
Errors
A
400 and a 402 both mean the request as written can never succeed, so retrying it unchanged just spends rate limit. Narrow the window or coarsen the granularity instead.
Covering a whole workspace
The endpoint is per template. For a workspace-wide view, list your templates with and call metrics for each one, sequentially. At 1 to 5 requests per second, fanning out concurrently just earns a429.