grithdocs

Analytics

The two local analytics-v2 routes, what each tier gets, and the freshness block that tells you how far behind the projection is.

MethodPathTier
GET/api/analytics/v2/freeCommunity
GET/api/analytics/v2/proPro

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.

GET /api/analytics/v2/free
{
"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

Last updated: 2026-08-24Edit this page on GitHub →