Skip to main content
A project analytics query returns one aggregate: a count, a percentile, a sum, or an estimated cost, optionally grouped by time or by a field. You send it over REST or the Lemma Model Context Protocol (MCP) server. This page explains how a query is built, then shows examples you can copy. Use a query when one number or one breakdown answers the question. For a full Analytics panel, request a preset view instead.
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 as trace.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.
On MCP, call 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:
On MCP, call 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 one value
  • in takes an array of values
  • is_null and is_not_null take no value
A row must match every filter to be counted.

Group and order

A group_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 chose
  • truncated: true when the rows reached limit, or limits.max_rows if you set no limit
  • window: the start and end_exclusive that were queried
When 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 as query.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:
Each row is one day. p95_ms is in milliseconds.

Tools that fail most

Rank tools by failed calls. On tool.called, error is 1 when the tool span failed and 0 otherwise, so its sum is the failure count:
Divide 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:
The row reports the estimate in 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 on agent_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.

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.