Skip to main content

Analytics

The explorer, the boards, and GET /api/analytics/query read the same request log. Shadow rows are excluded from the totals. Dollar totals sum only rows whose cost status is priced, cache_hit, or legacy. unknown_price and no_usage count as unpriced_requests and add nothing to cost_usd. The JSON body includes unpriced_requests and unpriced_share so a gap in the price table is visible.

Explorer​

The dashboard route is #analytics. The hash is the query: organization, from, to, metrics, group-by, granularity, top, chart, filter, and compare. Opening a saved view writes that hash back.

GET and POST /api/analytics/views?org_id= need analytics.read. A view stores the normalized query. label:Issue is stored as label:issue. Each owner can keep 100 views in an org; the next create is 409 view_limit. A private view owned by someone else is 404. A shared view can be changed by its owner or by a caller with org.update. Creating or deleting a shared view is audited. Mutations use the admin CSRF cookie.

Query API​

GET /api/analytics/query needs analytics.read.

ParameterMeaning
org_idOrganization. Required
from, toRFC3339, from before to
metricsOne to six names, comma-separated
group_byAt most two dimensions. label:<key> is lowercased
granularitynone (default), hour, day, week, month. hour allows at most 14 days
topDefault 20
tzIANA zone. Omitted means the org zone, then UTC
filterYQL, for example label.ci.pipeline = "9001"
compareprevious adds the preceding window
formatcsv returns a spreadsheet. Anything else is JSON

Metric names: requests, tokens_in, tokens_out, cache_read_tokens, cost_usd, unpriced_requests, errors, retried_requests, fallback_requests, error_rate, cache_hit_rate, prompt_cache_ratio, cost_per_request, latency_p50, latency_p95, ttft_p50, ttft_p95, sessions.

Dimensions: model, model_requested, endpoint, team, agent, task, status_class, cache, and label:<key>.

curl -fsS -b "$ADMIN_COOKIE" \
"$ADMIN/api/analytics/query?org_id=$ORG&from=2026-09-01T00:00:00Z&to=2026-09-08T00:00:00Z&metrics=cost_usd,requests,unpriced_requests&group_by=label:ci.pipeline&granularity=day&top=20&filter=label.ci.pipeline%20%3D%20%229001%22"

CSV is the same URL with &format=csv.

A filter, a non-additive metric, or a range the rollup cannot answer reads request_logs directly. That path covers at most 93 days. A query with an empty filter, additive metrics, and granularity hour, day, week, or month can use the rollup and then covers up to 400 days. The JSON field source is rollup in that case.

Rollups and price coverage​

loggerd rewrites analytics_rollup_hourly and label_rollup_hourly every 5 minutes for [now−3h, now+1h), and at 03:17 UTC recomputes 7 days in daily chunks. A grouped query that hits the rollup can lag by one refresh. YABANIN_ROLLUPS=0 stops the loop. Label rollups keep only keys listed in the org policy's rollup_keys.

cost_usd ignores unpriced rows. unpriced_requests counts them. After yb pricing sync --apply, database prices reach the gateway within 5 minutes. Until then those requests stay in the unpriced count.

Boards​

The CI board groups cost by pipeline, job name, and trigger, and plots daily cost for requests that have ci.system. A pipeline with no ci.pipeline label does not appear on the pipeline panel. Other boards cover models, tools, and hygiene. A panel whose required dimension was never seen is hidden rather than filled with a zero.