Skip to main content

Worked analytics queries

Every operation below is documented on this page with its parameters, but the bodies you branch on — the KPI envelope, the breakdown rows, the totals line — are easiest to learn as one reader chain: pick a metric/summary endpoint → filter by period and channel → page through the breakdown rows → read the totals row. The samples below walk that chain end to end and show the full response envelope at each step. Scope of this overlay:
  • Per the language note above, these samples show cURL and TypeScript — the two most-requested languages. The other four tabs appear on the endpoint blocks themselves.
  • The full endpoint catalogue with every filter combination lives at the stats API guide; recurring email delivery of the same data (schedule, recipients, cadence) is covered in scheduled reports. This overlay stays envelope-accurate: request, response, and the errors that decide what you branch on — nothing else.
All analytics reads are tenant-scoped and read-only. They degrade to a zeroed summary or an empty rows array — still a 200 — during a transient storage blip, so a poll loop or an always-on dashboard never flashes a 5xx; design your polling against that contract.

1. Pull a KPI summary

GET /api/v1/analytics/messages returns the headline KPIs for a period — sent, delivered, failed, read, the derived rates, and the average delivery time — plus a bucketed time series for charting. Filter the window with start_date / end_date (or a days lookback) and narrow to one channel with channel.
200
Adopt the same envelope shape everywhere on this page: all counters live under data.totals, chartable buckets under data.time_series (or a named breakdown array), and the echoed granularity under data.group_by. The cost counterpart — GET /api/v1/analytics/costs — answers with the same totals + time_series envelope plus a by_channel breakdown.

2. Page a breakdown series

Breakdown endpoints return the counters you just read, re-sliced per dimension — channel, destination country, or error code behind failures. Page through the list endpoints that carry a cursor (for example goals): keep the same filters, pass the previous page’s nextCursor back as cursor, and stop when it comes back null. GET /api/v1/analytics/goals
Page 1:
200
Page 2 — pass cursor=cur_8Kw2mQ1z; when this page’s nextCursor is null you have the full set:
200
The fixed-shape breakdowns — GET /api/v1/analytics/messages/by-channel, /by-country, and /messages/errors — are single-response reads with no cursor; each returns its full ranked array (channels, countries, errors) so you render them directly.

3. Prebuilt report read (and the empty-period shape)

GET /api/v1/analytics/scheduled-reports lists the recurring reports already scheduled for your organization — the prebuilt views the scheduler emails on a cadence — with each report’s type, frequency, recipients, and its last- and next-send timestamps.
200
The empty period is a zeroed summary, not an error. Choose a window with no traffic (or hit the degraded path during a storage blip) and the KPI endpoint still returns 200 with zeroed totals and an empty series — test your client against this shape so a quiet period never parses as a failure:
200

4. Errors

Two branches cover the mistakes callers actually hit on this surface. Validation — unrecognized filter value. Query params are validated before any query runs. A failed validation returns 422 VALIDATION_ERROR with error.details.issues listing each rejected field — for example a group_by outside the allowed set (hour / day / week / month):
422
Role — reads are gated to specific roles. The analytics reads on this page require an org role of owner, admin, developer, or viewer (writes such as creating goals or scheduled reports narrow to owner, admin, and developer); a key presented without one of those roles is rejected with 403 INSUFFICIENT_PERMISSIONS and a message naming the accepted roles:
403
Treat both as terminal, not retriable: the 422 needs a corrected filter before you re-send, and a 403 will not succeed until the role changes.