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.
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
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’snextCursor back as
cursor, and stop when it comes back null.
GET /api/v1/analytics/goals
200
cursor=cur_8Kw2mQ1z; when this page’s nextCursor is null
you have the full set:
200
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
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 returns422 VALIDATION_ERROR
with error.details.issues listing each rejected field — for example a
group_by outside the allowed set (hour / day / week / month):
422
403 INSUFFICIENT_PERMISSIONS and a message naming the accepted roles:
403
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 baredata 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 reportsdelivered 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 aprogress 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-goalsreturns{ 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-goalscreates a goal — body:name,metric(csat|nps|ces),targetwithin the metric’s scale, optionalsurvey_id,window_days(1–365, default 30),enabled— and returns201with the goal object; the baseline is captured from live responses at creation.PATCH /api/v1/analytics/satisfaction-goals/{id}updates any ofname,target,survey_id,window_days,enabled(at least one;metricis immutable) and returns200with the recomputed goal object.DELETE /api/v1/analytics/satisfaction-goals/{id}stops tracking the target and returns200with:
200
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