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.
  • The closing section, Response envelopes for the generated shells, groups the remaining analytics operations — dashboards, carrier and route truth, satisfaction goals, portfolio intelligence, and scheduled-report writes — into five panels with one copyable envelope each.
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.

Response envelopes for the generated shells

The endpoint blocks on this page carry parameters and status codes, but a number of operations render with a bare data shell in the generated reference. This section hands you one copyable, field-accurate envelope per operation, grouped into five panels so you land on the one you need: dashboards and widgets, carrier and route truth, satisfaction goals, portfolio and campaign intelligence, and scheduled-report writes. Every envelope below is tenant-scoped, wraps in the standard meta block (request_id, timestamp) shown in sections 1–4 — omitted here for readability — and degrades to zeroed or empty collections with a 200 during a transient storage blip, per the contract at the top of this page.

Dashboards and widgets

GET /api/v1/analytics/dashboards/widgets is the palette for the no-code dashboard builder: every composable widget with the read-only source endpoint that backs it, plus the grid constraints the builder enforces. It takes no query parameters.
200
GET /api/v1/analytics/deliverability answers with the per-channel deliverability panel: totals, the canonical rates, a bucketed series, and the top failure reasons and countries for one channel (channel required; window 24h|7d|30d; optional groupBy).
200
GET /api/v1/analytics/deliverability-health-center is the cross-channel deliverability cockpit: one composite 0–100 score over the five per-channel sender-reputation cards (SMS route quality, email domain and IP authentication, WhatsApp quality rating, RCS sender status, voice call quality), each independently fail-safe, plus the worst-first remediation list. days (1–90, default 7) sizes the SMS route-quality slice.
200
GET /api/v1/analytics/funnel/{step}/drop-reasons returns the ranked carrier rejection codes behind one funnel step — the same drop-reason vocabulary as the errors breakdown in section 2 — so a step’s drop-off and its causes parse with one shape.

Carrier and route truth

These four reads are the “the DLR says delivered, but is it?” panel. The silent-drop inference compares the carrier-claimed delivery rate against an engagement signal over a sliding window: a destination that reports delivered but never replies, answers, or raises any other engagement inside the window counts toward inferred_dropped, and a carrier whose engagement-backed rate stays under the drop-rate threshold over a sufficient sample flips silent_drop_suspected to true. The same sliding-window fields the handler returns are echoed on every response — dropRateThreshold (default 92), silentWindowHours (default 4 for the SMS DLR grace window), and minSampleSize — so you can audit each verdict against your own thresholds; the account health score uses that same 24-hour engagement window for its Engagement subscore. GET /api/v1/analytics/per-mno-delivery buckets outbound SMS or voice traffic by destination carrier. This row is a real silent-drop verdict: the carrier claims 1,178 delivered of 1,204 terminal, but only 610 ever showed engagement, so 568 sends are inferred dropped and the carrier trips the threshold.
200
GET /api/v1/analytics/per-route-delivery is the connector-side companion: the same counters grouped by the carrier connector that carried each message (the wholesale softswitch, a per-SMPP credential, or the connector the retry pipeline fell back to), paginated with page / pageSize. totals.degraded covers every route, not just the page.
200
GET /api/v1/analytics/route-dlr-anomaly classifies every outbound SMS route (carrier × destination country) against its learned 14-day delivery-rate baseline — drop, normal, or insufficient_data — so a carrier silently degrading one route surfaces before your customers notice. No query parameters.
200
GET /api/v1/analytics/route-latency-slo catches the two blind spots the DLR-rate detector cannot see: a route that still delivers ~100% but has slowed to minutes (p95 latency against the 300 s SLO), and a route whose DLRs silently stopped arriving (share of sends stuck non-terminal past the receipt grace). Each route lists the thresholds it breached.
200

Satisfaction goals

Satisfaction goals put a target on one survey metric — CSAT (percent), NPS (points), or CES (score) — and recompute progress from your live survey responses on every read; nothing is stored but the target itself. Every operation in this cluster parses the same goal object: the stored fields plus a progress envelope comparing the live value against your target.
200
current is the live metric value over window_days (null until responses exist), baseline is the value captured when the goal was created, and progress_pct measures the journey from baseline to target, clamped to 0–100; attained flips to true once current reaches the target. The five operations wrap that object:
  • GET /api/v1/analytics/satisfaction-goals returns { items, total } of the goal object above.
  • GET /api/v1/analytics/satisfaction-goals/{id} returns the single goal object (404 when the id is not one of yours).
  • POST /api/v1/analytics/satisfaction-goals creates a goal — body: name, metric (csat | nps | ces), target within the metric’s scale, optional survey_id, window_days (1–365, default 30), enabled — and returns 201 with the goal object; the baseline is captured from live responses at creation.
  • PATCH /api/v1/analytics/satisfaction-goals/{id} updates any of name, target, survey_id, window_days, enabled (at least one; metric is immutable) and returns 200 with the recomputed goal object.
  • DELETE /api/v1/analytics/satisfaction-goals/{id} stops tracking the target and returns 200 with:
200
Writes require owner, admin, or developer; reads follow the analytics-read hierarchy. A target outside the metric’s scale (CSAT 0–100, NPS −100–100, CES 0–10) is a 422 VALIDATION_ERROR naming the accepted range. (The conversion-goal cluster — GET /analytics/goals, paged in section 2 — is the separate “which touchpoint drove a sale” surface.)

Portfolio and campaign intelligence

GET /api/v1/analytics/sentiment rolls per-message sentiment up across conversations: a portfolio-wide totals line, a chronological trend, and per-channel / per-agent / per-language / per-resolution breakdowns, each carrying a net sentiment index in −100–100.
200
GET /api/v1/analytics/sms-click-through is the channel-level click-through panel over your tracked short links: sends, tracked sends, raw and verified clicks, and both CTRs per campaign (or per queue with group=queue), paginated like the route breakdown above. click_rate is null for a bucket that carried no tracked links.
200
GET /api/v1/analytics/telegram returns the Telegram-only KPIs the generic rollups cannot express: bot-blocked rate (Bot API 403 user-blocked family), custom-emoji rate, and the DM-vs-group split with a per-chat-type breakdown.
200
GET /api/v1/analytics/topic-intelligence answers “what are customers contacting us about, and is it rising or falling” across voice, SMS, WhatsApp, email, and web chat: up to 50 trending contact reasons with current-vs-prior volume, the per-channel split, CSAT / deflection / handling-time rollups, and linked example threads.
200
GET /api/v1/analytics/usage-anomalies compares the last complete hour of each usage family (SMS throughput and telco cost, voice throughput and telco cost) against a learned 72 h baseline and flags spike or drop — the tenant-wide stall or runaway-cost surge the per-route detectors are too narrow to see.
200

Scheduled reports

POST /api/v1/analytics/scheduled-reports/{id}/send-now marks one scheduled report due immediately; the dispatcher emails it on its next tick instead of waiting for the cadence, and the UI polls last_sent_at to show delivery progress. Returns 200 with a queue acknowledgement (404 when the id does not resolve to one of your reports); requires owner or admin and supports the Idempotency-Key header.
200
PATCH /api/v1/analytics/scheduled-reports/{id} updates any of name, type, frequency, recipients, filters, enabled, timezone (at least one). Changing frequency re-anchors the cadence from the last send; the derived next tick never lands in the past. Returns 200 with the updated report:
200