Skip to main content
The Gateway Model Metrics Query API provides a flexible way to query Gateway model and virtual-model metrics for usage, performance, cost, and user activity. You can retrieve either distribution (aggregated) or timeseries results with powerful filtering and grouping.
This page covers datasource: "modelMetrics". For other datasources, see MCP, Guardrail, Cache, Routing, and Agent metrics.
All requests go to a single endpoint:
Send JSON with 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

Authenticate with your TrueFoundry API key. You can use either a Personal Access Token (PAT) or Virtual Account Token (VAT).
  1. Personal Access Token (PAT): Go to Access → Personal Access Tokens in your TrueFoundry dashboard
  2. Virtual Account Token (VAT): Go to Access → Virtual Account Tokens (requires admin permissions)
For detailed authentication setup, see our Authentication guide.

Quick start

By default, the API returns metrics for both models and virtual models. To restrict to one, add {"fieldName": "virtualModelName", "operator": "IS_NULL", "value": true} for model-only metrics, or value: false for virtual-model-only metrics.
The virtual-model column has two aliases. In 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.
Aggregated model metrics including request counts, token totals, p99 latency, and cost grouped by model:

API reference

Post JSON to the endpoint above with Authorization: Bearer <your_api_key> and Content-Type: application/json.

Request parameters

string
required
ISO 8601 timestamp marking the inclusive lower bound of the query window (e.g. "2026-04-21T00:00:00.000Z").
string
required
ISO 8601 timestamp marking the exclusive upper bound of the query window (e.g. "2026-04-22T00:00:00.000Z").
string
required
The data source to query. Use "modelMetrics" for Gateway model metrics.
string
required
The type of query to execute:
  • "distribution": returns aggregated rows (one row per groupBy combination).
  • "timeseries": returns time-bucketed rows (one row per bucket per groupBy combination). Requires interval.
array
Array of { type, column } objects describing the aggregations to compute. When omitted, only the implicit total = COUNT(*) is returned.
All scalar and percentile aggregation types apply to every column above.
array
Array of field names to group results by. Custom metadata keys are supported with a metadata. prefix (e.g. "metadata.environment").
When 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.
array
Array of filter objects, AND-combined. See Filtering below for the full operator reference and the per-field allow-list.
string
Required for timeseries queries. Bucket size as <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.
number
deprecated
Deprecated alias for 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.
For standard datasource fields, use fieldName:
Most string fields accept the full string operator set: 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.For cache-specific fields (cacheType, cacheNamespace, cacheLookupStatus, and the cache token columns), see Cache Metrics.
String field operatorsNumeric field operatorsBoolean field operatorsArray field operators (used by team)
Custom metadata filtering and grouping. Every datasource supports filtering and grouping by custom request-metadata keys:
  • Filter: { "metadataKey": "environment", "operator": "EQUAL", "value": "prod" }
  • Group: include "metadata.environment" in the groupBy array (string literal, prefix is metadata.).
Metadata fields are treated as strings; use the string field operators above.Implicit team unnesting. When 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 the json body is shown; the request wrapper is identical to the Quick start.
By default, model metrics include both models and virtual models. The examples pin the model side with {"fieldName": "virtualModelName", "operator": "IS_NULL", "value": true}. To target virtual models, flip the value to false and swap groupBy: ["modelName"] for groupBy: ["virtualModel"] (the alias used in groupBy/aggregations).

Distribution examples

Request counts grouped by model:
Total input and output tokens per model:
p50, p90, and p99 latency grouped by model:
Group by model and a custom metadata key:
Group by multiple dimensions (model + subject):
Requests slower than 1 second, grouped by model:
Requests within a latency band:
Combine input and output token thresholds:
Filter to specific teams using array operators:
Restrict by a custom metadata value:
Only requests routed through a virtual model. Note virtualModel in groupBy/aggregations but virtualModelName in filters:
Combine multiple filter types:

Timeseries examples

Every timeseries query must include interval (or the deprecated intervalInSeconds). Buckets are expressed as <positive integer> <unit> strings like "5 minute", "1 hour", or "1 day".
Hourly request counts:
Fine-grained traffic with 5-minute buckets:
Hourly counts grouped by model:
Track p99 latency regressions per model:
Track per-team adoption over time:
Restrict to specific models and a latency threshold:
Cost burn-down per model over time:
Group hourly counts by a custom metadata key:
Daily traffic across a 7-day window:
Filters + groupBy + metadata together:

Response format

Every successful response has the same outer shape:
  • total: implicit COUNT(*) 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 per groupBy entry. The key is the lowerCamelCase form of the underlying column. Two special mappings:
    • userEmail and virtualaccount both map to createdBySubjectSlug in the response (the underlying column is CreatedBySubjectSlug, differentiated by CreatedBySubjectType).
    • team maps to team (the value is a single unnested scalar, not an array). All other groupBy keys preserve their lowerCamelCase name.
  • startTimestamp / endTimestamp: present only for timeseries responses. Bucket start and end as ISO 8601 timestamp strings; endTimestamp equals the next bucket’s startTimestamp. Distribution responses omit both.
If groupBy is empty or omitted, the response collapses to a single row (or one row per timeseries bucket) summarising every request inside the window.
Virtual-model rows surface under the virtualModelName key in the response (not virtualModel), because the response key is the lowerCamelCase of the underlying database column.

Error responses

A malformed query returns 400 Bad Request:
Common causes of 400:
  • Operator not allowed on this field, for example, EQUAL on a field that supports only IN/NOT_IN.
  • Missing required value (or wrong shape, e.g. scalar where array is expected for IN / BETWEEN).
  • Unknown field name for the datasource.
  • Invalid interval format (compound expressions, unrecognised unit, non-positive integer).
  • Missing required interval for a timeseries query.
Other status codes:
  • 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.