Analytics
The two local analytics-v2 routes, what each tier gets, and the freshness block that tells you how far behind the projection is.
| Method | Path | Tier |
|---|---|---|
| GET | /api/analytics/v2/free | Community |
| GET | /api/analytics/v2/pro | Pro |
Both are sensitive reads: send the dashboard token as x-grith-csrf once one exists. Both
read a projection that lives inside the audit database - there is no separate analytics
store, and no endpoint that computes anything from scratch.
Free is a first-class server response, not a client-side mask over the Pro payload. The
feature check on /pro runs before any storage access, so a Community caller never
receives richer rows for a browser to hide.
GET /api/analytics/v2/free
A 7-day window.
{
"protocol_version": 2,
"schema_version": 1,
"access": "free",
"window": { "start_day": "2026-08-18", "end_day": "2026-08-24", "current_day_partial": true },
"decisions": {
"total": 4812, "allow": 4702, "queue": 96, "deny": 14,
"allow_rate_ppm": 977140, "queue_rate_ppm": 19950, "deny_rate_ppm": 2909
},
"chain_health": "healthy",
"latest_audit_record_at": "2026-08-24T09:14:23.481920Z",
"recent_queue_and_deny": [],
"freshness": {
"materialized_through_at": "2026-08-24T09:14:00.000000Z",
"materialized_through_sequence": 48120,
"dirty_day_count": 0,
"rebuilding": false,
"gap_count": 0
},
"pro_available": false
}Rates are parts per million, not percentages. Timestamps are RFC 3339 with exactly six
fractional digits - a wire contract, because row checksums hash the serialised form.
chain_health is healthy, gap, broken, quarantined or unknown, and
recent_queue_and_deny holds up to 20 recent security events. pro_available reports
whether this install's licence would allow the Pro route.
GET /api/analytics/v2/pro
The same window machinery over 30 and 90 days, with rollups instead of a single count:
usage_rows, filter_rows, session_rows, llm_rows, destination_rows,
security_events, plus windows, generated_at, the same freshness block,
export_formats (json and csv), export_max_days (90) and truncated.
truncated is true when a rollup family was clipped to its schema row cap. The rows that
get dropped are the oldest days, never a silent slice out of the middle.
Without a Pro licence this route is 403 FEATURE_GATED with feature set to
usage_analytics.
Freshness, and why it is on every response
Both routes run a bounded catch-up before reading - at most four batches, at most eight
days - so a first read over a large backlog cannot hold the storage lock for minutes. The
daemon's background worker drains the rest. freshness is how the response tells you it
did not finish: materialized_through_at and materialized_through_sequence are how far
the projection has been built, dirty_day_count is how many days still need rebuilding,
and rebuilding is true while a rebuild is in flight.
To force the projection to be rebuilt from the audit records, run
grith audit rebuild-analytics.
503 ANALYTICS_UNAVAILABLE
Returned when the process holding the audit database is an older grith that does not
maintain the projection. The message names the fix: grith daemon restart.
See also
- Analytics (Pro) - what syncs, what never leaves the machine
grith analytics