You need an organization API key whose scope is read or admin, plus the project id. An ingest-only key cannot call these routes. A key limited to one project works only for that project. Create the key under API Keys in the Lemma dashboard.
Find events and fields in the catalog
Every query reads one event, such astrace.processed or tool.called. The catalog lists each event with the fields you can filter and group by (dimensions), the fields you can aggregate (measures, each with its allowed aggs), and any cost_measures. It also lists filter_ops, buckets, and limits.
describe_project_analytics with project_id. A field that isn’t in the catalog can’t be queried.
Send a query
POST the query body to /projects/{project_id}/analytics/query:
query_project_analytics with project_id plus the same body. MCP uses camelCase for three keys: endExclusive, groupBy, and orderBy. Field names such as agent_name stay snake_case in both.
Build the query body
A body names an event, a window, and at least one measure. Everything else narrows or shapes the result.Window
start and end_exclusive must fall on a whole second, in UTC. The end is exclusive, so a window ending at 2026-09-30T00:00:00.000Z stops before that instant. The window can’t be longer than the catalog maximum in limits.
Measures
Each measure is{ "as": "alias", "agg": "aggregation", "field": "field" }. count counts rows and takes no field. Other aggregations, such as sum, avg, and p95, need a numeric field that lists that aggregation in its catalog aggs. uniqExact counts distinct values of a field.
A cost measure, trace_cost or generation_cost, takes no field. A query can have one cost measure. See Cost estimates.
An alias names the column in each result row. It starts with a letter, then uses lowercase letters, digits, and underscores, up to 41 characters.
Filters
Each filter is{ "field": "field", "op": "operator", "value": "value" }. The operators are filter_ops in the catalog:
- A comparison operator, such as
eq, takes onevalue intakes an array of valuesis_nullandis_not_nulltake novalue
Group and order
Agroup_by entry is either a time bucket, { "bucket": "day", "as": "day" }, or a field, { "field": "tool", "as": "tool" }. The bucket names are in the catalog buckets.
An order_by entry is { "measure": "alias", "direction": "desc" }. The alias can name a measure or a group. Without order_by, grouped rows are sorted by the group.
Join
join adds measures from one other event, matched on a field both events have, such as agent_name. Cost measures belong on the primary event only.
Read the response
A response has three keys:rows: one object per group, keyed by the aliases you chosetruncated: true when the rows reachedlimit, orlimits.max_rowsif you set no limitwindow: thestartandend_exclusivethat were queried
truncated is true, further rows exist. Narrow the filters or raise limit. REST and MCP return the same snake_case shape.
Lemma returns HTTP 400 with the reason for an invalid query, and HTTP 404 for an unknown project. A query that runs longer than 15s fails.
Examples
Each example below is a complete body. Save it asquery.json and send it with the request in Send a query.
Daily volume and latency for one agent
Count one agent’s traces per day, with the 95th percentile of root duration:p95_ms is in milliseconds.
Tools that fail most
Rank tools by failed calls. Ontool.called, error is 1 when the tool span failed and 0 otherwise, so its sum is the failure count:
failures by calls for the failure rate. This row is 4.5%:
Estimated cost by model
Estimate generation cost per model.generation_cost needs a model group with the alias model:
estimated_cost_usd, not under the cost alias:
priced: false means Lemma has no list price for that model, and estimated_cost_usd is null.
Traces and tool calls per agent
Join tool calls to traces onagent_name:
Cost estimates
trace_cost and generation_cost are estimates from recorded token counts and public list prices, not an invoice. A backfilled row keeps the price from that backfill.
trace_cost runs on trace.processed and needs no model group. Its row includes estimated_cost_usd and cost_lower_bound. When cost_lower_bound is true, the estimate can be low, and cost_lower_bound_reasons lists why.
Preset views
GET /projects/{project_id}/analytics returns one dashboard panel. Pass one view. MCP has one tool for each view. View names, parameters, and responses are on Get project analytics.
Related pages
Analytics
Dashboard charts for volume, latency, errors, tools, models, and cost.
Analytics catalog
Events, fields, filters, and limits for a project query.
Query project analytics
The REST operation for one aggregate.
Analytics telemetry
SDK fields the aggregates are computed from.