Authorization: Bearer <your_api_key> and Content-Type: application/json.
Access control
Access to metrics is governed by the data access rules configured by your tenant. The server applies these rules automatically based on the caller’s identity—you don’t pass any RBAC or scoping fields in the request. What a caller can query (their own data, their team’s data, or tenant-wide data) depends entirely on the rules an admin has set up. See Configure Data Access for how these rules are defined and evaluated.Authentication
Get your API key
Get your API key
- Personal Access Token (PAT): Go to Access → Personal Access Tokens in your TrueFoundry dashboard
- Virtual Account Token (VAT): Go to Access → Virtual Account Tokens (requires admin permissions)
Quick start
groupBy and aggregations[].column use virtualModel. In filters[].fieldName and in response keys, the name is virtualModelName. They refer to the same underlying database column.- Real model distribution
- Virtual model distribution
- Real model timeseries
API reference
Post JSON to the endpoint above withAuthorization: Bearer <your_api_key> and Content-Type: application/json.
Request parameters
"2026-04-21T00:00:00.000Z")."2026-04-22T00:00:00.000Z")."modelMetrics": combined table containing both real model and virtual model rows.
"realModelMetrics": dedicated table with real model metrics only (novirtualModelNamefilter needed). Includes cache-related columns."virtualModelMetrics": dedicated table with virtual model metrics only (novirtualModelNamefilter needed). Does not include cache-related columns (cacheType,cacheNamespace,cacheLookupStatus,cacheLookupLatencyMs,potentialCostSavings) — use"realModelMetrics"or Cache Metrics for cache data.
"distribution": returns aggregated rows (one row pergroupBycombination)."timeseries": returns time-bucketed rows (one row per bucket pergroupBycombination). Requiresinterval.
{ type, column } objects describing the aggregations to compute. When omitted, only the implicit total = COUNT(*) is returned.Supported aggregation types
Supported aggregation types
Supported aggregation columns
Supported aggregation columns
cacheLookupLatencyMs, potentialCostSavings) are only available on "realModelMetrics" and "modelMetrics". They do not exist on "virtualModelMetrics".metadata. prefix (e.g. "metadata.environment").Available group-by fields
Available group-by fields
groupBy contains userEmail (without virtualaccount), the server auto-injects WHERE CreatedBySubjectType = 'user'. virtualaccount alone auto-injects 'virtualaccount'. When both appear, scope it yourself with createdBySubjectType if needed.<positive integer> <unit>, where <unit> is one of second, minute, hour, day, week, month, year (with or without a trailing s). Examples: "30 second", "5 minute", "1 hour", "1 day". Compound expressions like "1 hour 30 minute" are rejected.interval. Accepts a positive integer number of seconds (e.g. 3600 for hourly). Prefer interval in new code. If both are provided, interval wins.Filtering
Filters narrow down the rows that go into each aggregation and group. They are AND-combined; there is no OR-group support. The server enforces a per-field operator allow-list, so the exact subset of operators you can use depends on the field.- Field filters
- Metadata filters
fieldName:Filterable fields and allowed operators
Filterable fields and allowed operators
EQUAL, NOT_EQUAL, IN, NOT_IN, STRING_CONTAINS, STRING_NOT_CONTAINS, STRING_STARTS_WITH, STRING_NOT_STARTS_WITH, STRING_ENDS_WITH, STRING_NOT_ENDS_WITH. A few fields are narrower: providerAccountType and createdBySubjectType are comparison-only (no STRING_* operators), traceId is equality-only, and virtualModelName additionally supports IS_NULL.cacheType, cacheNamespace, cacheLookupStatus, and the cache token columns), see Cache Metrics. These fields are only available on "realModelMetrics" and "modelMetrics" — they do not exist on "virtualModelMetrics".Filter operators reference
Filter operators reference
team)Custom metadata, team unnesting, and combining filters
Custom metadata, team unnesting, and combining filters
- Filter:
{ "metadataKey": "environment", "operator": "EQUAL", "value": "prod" } - Group: include
"metadata.environment"in thegroupByarray (string literal, prefix ismetadata.).
team is in groupBy (or used as the column of an aggregation), the server transparently UNNESTs the Teams array CTE before applying RBAC. Callers don’t need to do anything extra. Rows whose Teams array is NULL or empty drop out naturally.Combining multiple filters. Filters are AND-combined:Query examples
Every example posts a JSON body to the endpoint above. To keep the snippets short, only thejson body is shown; the request wrapper is identical to the Quick start.
"realModelMetrics" or "virtualModelMetrics" as the datasource. These dedicated tables contain only real-model or virtual-model rows respectively, so no virtualModelName filter is needed.The legacy "modelMetrics" datasource (which combines both) is deprecated. If you still use it, filter with {"fieldName": "virtualModelName", "operator": "IS_NULL", "value": true} for real models, or false for virtual models.Distribution examples
Count by model name (real)
Count by model name (real)
Sum tokens by model (real)
Sum tokens by model (real)
Latency percentiles by model (real)
Latency percentiles by model (real)
Group by metadata (real)
Group by metadata (real)
Multi-dimensional grouping (real)
Multi-dimensional grouping (real)
Filter high-latency requests (real)
Filter high-latency requests (real)
Filter by latency range (real)
Filter by latency range (real)
Filter by token counts (real)
Filter by token counts (real)
Filter by team (real)
Filter by team (real)
Filter by metadata (real)
Filter by metadata (real)
Virtual-model metrics
Virtual-model metrics
Complex filter combination (real)
Complex filter combination (real)
Timeseries examples
Every timeseries query must includeinterval (or the deprecated intervalInSeconds). Buckets are expressed as <positive integer> <unit> strings like "5 minute", "1 hour", or "1 day".
Basic hourly counts (real)
Basic hourly counts (real)
5-minute intervals (real)
5-minute intervals (real)
Hourly counts by model (real)
Hourly counts by model (real)
Hourly p99 latency by model (real)
Hourly p99 latency by model (real)
Hourly counts by team (virtual)
Hourly counts by team (virtual)
Timeseries with model + latency filter (real)
Timeseries with model + latency filter (real)
Hourly cost by model (real)
Hourly cost by model (real)
Hourly metadata breakdown (real)
Hourly metadata breakdown (real)
Daily over a week (virtual)
Daily over a week (virtual)
Complex timeseries query (real)
Complex timeseries query (real)
Response format
Every successful response has the same outer shape:total: implicitCOUNT(*)for the row. Always present.<aggregationKey>: one key per requested aggregation. The key is<type><Column>in camelCase (e.g.sumLatencyMs,p99LatencyMs,countModelName,countDistinctToolName).<groupByKey>: one key pergroupByentry. The key is the lowerCamelCase form of the underlying column. Two special mappings:userEmailandvirtualaccountboth map tocreatedBySubjectSlugin the response (the underlying column isCreatedBySubjectSlug, differentiated byCreatedBySubjectType).teammaps toteam(the value is a single unnested scalar, not an array). All othergroupBykeys preserve their lowerCamelCase name.
startTimestamp/endTimestamp: present only for timeseries responses. Bucket start and end as ISO 8601 timestamp strings;endTimestampequals the next bucket’sstartTimestamp. Distribution responses omit both.
Distribution response example
Distribution response example
Timeseries response example
Timeseries response example
groupBy is empty or omitted, the response collapses to a single row (or one row per timeseries bucket) summarising every request inside the window.virtualModelName key in the response (not virtualModel), because the response key is the lowerCamelCase of the underlying database column.Error responses
A malformed query returns400 Bad Request:
Common causes and other status codes
Common causes and other status codes
400:- Operator not allowed on this field, for example,
EQUALon a field that supports onlyIN/NOT_IN. - Missing required
value(or wrong shape, e.g. scalar where array is expected forIN/BETWEEN). - Unknown field name for the datasource.
- Invalid
intervalformat (compound expressions, unrecognised unit, non-positive integer). - Missing required
intervalfor a timeseries query.
401 Unauthorized: missing or invalid bearer token.403 Forbidden: caller does not have permission for the requested scope.500 Internal Server Error: unexpected server error while executing the query.