Per-group analytics endpoints
four reads over the rollups: daily, commands, llm, summary. Bearer token from /login plus group_admins membership, 404 (not 403) for a group you do not administer, bounded windows, no fleet-wide endpoint - every query is single-shard
- v1 scenarios
- —
- Ported
- —
- Green
- —
- Failing
- —
four reads over the rollups: daily, commands, llm, summary. Bearer token from /login plus group_admins membership, 404 (not 403) for a group you do not administer, bounded windows, no fleet-wide endpoint - every query is single-shard
What it does
Nothing in a chat — this one is HTTP. A group's admins can read their own group's numbers: what happened per day (messages, joins, captchas, errors, latency), which commands actually get used, what the AI features cost, and one summary of the three. The data has been collected and rolled up nightly since the beginning; this is the first way for the people whose groups they describe to see it.
Behaviour that must not change
You can only read a group you administer. Membership of that group's admin list is the rule, and a group you do not administer answers 404, not 403 — otherwise anyone logged in could discover which chats this deployment knows about by watching which ids come back forbidden.
There is no "all groups" endpoint, and there should not be one. It would hand one tenant another's numbers, and on a sharded database it would have to ask every shard. Fleet-wide figures are the operator's and live in Grafana.
Every window is bounded — thirty days by default, a year at most — and asking
for a backwards or oversized range is an error rather than a quietly corrected
answer. A day the group was silent has no row at all: inventing a zero would be
indistinguishable from a real quiet day. The captcha solve rate is null, not
zero, when nobody was challenged, and latency figures are the window's worst
day rather than an average of percentiles, because averaging percentiles means
nothing.
How to verify it
qa/integration/test_analytics.py runs the three queries against real rollup
rows in Citus and asserts each one touches exactly one shard;
packages/cb-api/tests/test_analytics_endpoints.py covers the auth boundary —
including that an admin of one group cannot read another. Run with
python scripts/cb.py test and python scripts/cb.py test-integration. By
hand: log in through /login, then
curl -H "Authorization: Bearer $TOKEN" localhost:8000/groups/<id>/analytics/summary.
Fleet-wide analytics and the group directory, owners only
net-new: v1 had no analytics at all, and x_analytics_api deliberately has no fleet-wide endpoint. Seven owner-only reads (overview, daily, top groups, commands, llm, directory, tenant) behind a new admin:read scope that /oauth2/token grants only when the subject is a tenant owner or CB_OWNER_ID. 403 here rather than the group endpoints' 404: /admin has no chat id to hide. The one place in the codebase where a query does not filter on group_id - confined to cb_core/platform_analytics.py, aggregate-only, off the reply path, and argued in that module's docstring
Per-group audit trail
net-new: v1 kept no trail at all. group_audit_events is distributed on group_id (migration 0010) with a UUIDv7 key, so the page is a keyset read on one shard. Written by both surfaces - the Mini App's config endpoints and the Telegram config menu, /newrules and /newwelcome - and a failed audit write never fails the action it describes