All routes need an organization API key with read or admin scope, sent as
Authorization: Bearer your_organization_api_key. Analytics keeps 90 days of events.
Catalog
The catalog response has four keys. It’s the live source of truth for this page’s event and field lists:events: each event’sname,dimensions(field,type),measures(field,type,aggs), andcost_measuresfilter_ops: valid filter operatorsbuckets: valid time bucketslimits:max_group_by,max_measures,max_filters,max_order_by,max_in_values,max_rows,max_window_days
uniqExact, though the catalog lists aggs only for measures.
Events and fields
Every event also hasactor, surface, user_id, and api_key_id, which describe where the event was recorded. Missing string values are stored as '', not null.
trace.processed
One row per processed trace. Cost measure: trace_cost.
Root latency on this event is
root_duration_ms. The catalog also lists cost bookkeeping fields such as estimated_cost_basis, cost_priced_tokens, and cost_unpriced_models. This event has no queryable trace_id.
generation.completed
One row per model call. Cost measure: generation_cost. This event has no agent_name, so model use can’t be split by agent.
tool.called
One row per tool span.
span.errored
One row per failed span of any type. Fields: span_name, trace_id, span_id, all dimensions.
Issue lifecycle events
issue.created, issue.resolved, issue.dismissed, issue.reopened, issue.review_started, issue.flagged, issue.unflagged, and issue.slack_sent carry only the fields every event has. Count them by time bucket.
Query request
A query body names one event, a window, and at least one measure. REST uses the snake_case keys below. MCP usesendExclusive, groupBy, and orderBy for those three keys and takes project_id in the body. Unknown keys are rejected.
The window can’t exceed
max_window_days.
Measures
A measure is{ "as": "alias", "agg": "aggregation", "field": "field", "filter": {} }:
countcounts rows and takes nofielduniqExactcounts distinct values of any fieldsum,avg,min,max,p50,p95, andp99need a numeric field whoseaggslists the aggregationfilteris optional and holds one filter. The measure then aggregates only matching rows, while other measures see every row
trace_cost or generation_cost as agg and takes no field or filter. A query can have one cost measure, on the primary event. generation_cost also needs a model group whose alias is model.
An alias starts with a lowercase letter, then uses up to 40 lowercase letters, digits, or underscores. Aliases must be unique across groups and measures.
Filters
A filter is{ "field": "field", "op": "operator", "value": "value" }:
eq,neq,gt,gte,lt, andltetake one string or numberintakes a non-empty array of up tomax_in_valuesvaluesis_nullandis_not_nulltake novalue
agent_name, filter eq with "".
Groups and order
A group is{ "bucket": "day", "as": "day" } or { "field": "tool", "as": "tool" }. When you omit as, a bucket’s alias is bucket and a field’s alias is the field name.
An
order_by entry is { "measure": "alias", "direction": "asc" } or "desc". It names a group alias or a primary measure alias. Without order_by, grouped rows are sorted by the groups. Groups with no rows are absent from the result.
Join
A join adds measures from one other event:{ "from": "event", "on": "field", "filters": [], "measures": [] }. Its rules:
frommust differ from the primary event, andonmust be a field on both events- Lemma groups the joined event by
onand left-joins it to the primary rows on the primary group with that alias, so group the primary query byonand keep the default alias filtersapplies only to the joined event- Primary rows without a match get null joined measures
- Joined measures can’t be cost measures, reuse a primary alias, or appear in
order_by
trace.processed with tool.called on agent_name, and tool.called with generation.completed or span.errored on trace_id.
Query response
A query returnsrows, truncated, and window:
rows: one object per group, keyed by alias, or one object when there’s nogroup_by. Large integer results, such as counts and token sums, can be numeric stringstruncated: true when the row count reacheslimit, ormax_rowswithout a limit. More groups can existwindow: thestartandend_exclusivethat ran
Cost fields
Cost rows don’t use the cost measure’s alias. Ageneration_cost row has estimated_cost_usd (null when unpriced) and priced.
A trace_cost row has:
estimated_cost_usd,priced_tokens,total_tokens, andpriced_token_shareunpriced_modelsandpricing_fetched_atcost_source:stored,mixed, orquery_timecost_lower_boundandcost_lower_bound_reasons, fromunpriced_models,prices_unavailable,unbackfilled_traces,live_priced_breakdown_truncated, andmodel_rollup_truncatedmodel_breakdown_truncatedattribution:coverage(all,unwritten_only, ornone) andvalues, a map from model to estimated cost for traces priced at query time
Query errors
Lemma rejects an invalid query with HTTP 400 and adetail message:
An unknown project, or a key limited to another project, returns HTTP 404. A query that runs past 15s fails with a timeout.
Preset views
A preset view returns one precomputed dashboard payload, so its numbers match the Analytics page. Over REST, pass the view asview. On MCP, call the view’s tool.
Preset parameters are query parameters:
start: inclusive, ISO 8601, UTC, whole secondendExclusive: exclusive end, required where the table says so. REST also acceptsend_exclusiveend:model_scorecardonly, in place ofendExclusivegranularity:day(default),week, ormonth
/projects/{project_id}/agent-scorecard, are deprecated aliases for ?view=.
Preset responses
Each view returns these keys:window:totals(traces,p50,p95,error_rate,failed_runs,root_status_known,root_absent),latency(granularity,pointsofdate,p50,p95),coverage, andcostbase:traces,errors(span_errors,failed_runs,root_status_known,root_absent,tracesper date),tokens,cost,cost_by_model, andcost_by_model_coverageagent_scorecard:rowswithagent,traces,p50,p95,error_rate,root_status_known,tools_used,tokens,cost, and lower-bound flagstool_aggregates:comparisonrows (tool,calls,errors,error_rate,p50,p95,traces,output_tokens),volume,failing_volume, anderror_concentrationmodel_scorecard:rowswithmodel,traces,tokens,input_tokens,output_tokens,avg_speed,priced, andestimated_cost_usd, plus overall cost fields andcost_attribution_coverageroi:state(connect,waiting, orpopulated), lifetimecounts, and dailybaseseries forcaught,resolved,dismissed,traces,errors,artifact_iterations, andresolution_days
coverage in the window view reports, per capability (root_status, root_duration, agents, tokens, model, provider, cost_basis, tools), whether the project sends it at all and how many window traces have it (observed of eligible). A missing capability explains an empty metric; see Analytics telemetry.
A per-model estimated_cost_usd in model_scorecard can be null when part of the window was priced at write time. The overall figure stays complete; cost_attribution_coverage is all when the rows add up to it.
Related pages
Querying analytics
Build and send a query, with worked examples.
Analytics telemetry
SDK fields the aggregates are computed from.
Analytics
Dashboard charts the preset views back.
Lemma MCP server
Connect a coding agent to these tools.