Skip to content

Subject Analysis Query

Return chart-ready time series for the token subject.

POST /v1/subject/analysis/query

Use a Subject Token with the analysis rule. Tenant, project, subject type and subject ID come from the token. Obtain it through Grant Subject Token. Missing, invalid or expired tokens return 401; missing analysis returns 403.

Authorization: Bearer <subject-token>
Content-Type: application/json

Omit subject_type and subject_id to use the token identity. If supplied, they must match the token or the request returns 403. Subject constraints override generic subject filters. A billing-account filter can only narrow this subject’s data.

Field Contract
start_time Required inclusive start, Unix seconds.
end_time Required exclusive end, Unix seconds; must exceed start.
measures Required non-empty array: quantity, amount, quantity_avg, amount_avg, item_count, usage_event_count, event_count.
metrics Explicit metric-name array for metric measures. metrics takes precedence over metric.
metric Optional legacy single-metric selector.
billing_account_id Optional persisted billing-account ownership filter; never widens subject access.
subject_type / subject_id Optional; must match token identity.
filters Optional string equality map for dimensions/labels, e.g. {"model":"gpt-4.1-mini"}. billing_account_id is forbidden here.
group_by Optional array, up to 4 dimensions: bucket, metric, subject_type, subject_id, or label keys. Cannot group by billing_account_id.
limit Optional row limit; default 100, maximum 1000. Zero uses default; negative or above 1000 is invalid. No cursor pagination.
bucket_size Required positive duration Nm, Nh, Nd, or day (= 1d); auto is invalid.
timezone Optional IANA timezone; default UTC. Example: Asia/Shanghai. Local is invalid.

Metric selectors are intersected with the token allowlist. A wholly unauthorized selector returns an empty result. Omission does not mean “all metrics” for metric aggregation: select metrics explicitly. Event counts need no metric selector, cannot mix with metric measures, and cannot filter or group by metric. Event counts remain subject to token visibility. Unknown JSON fields are rejected.

The service always adds bucket to response grouping. Minute/hour buckets are fixed and Epoch-aligned. Day buckets follow calendar boundaries in timezone, anchored to midnight on the date containing start_time. Align the start to local midnight for complete day buckets; a mid-day start keeps the original filter and creates a partial first bucket. The maximum range is 31 local calendar-date differences.

Minimum bucket sizes: range ≤ 1 hour → 1m; above 1 hour and below 1 local day → 15m; 1–10 local days → 1h; above 10–21 days → 2h; above 21 days → 4h. Smaller buckets return 400 with analysis_bucket_too_small:. Invalid timezone errors start with invalid_timezone:.

Most queries filter occurred_at and exclude revoked usage. Pure event-count queries with fixed minute/hour buckets (or no bucket), subject-only grouping and event-index-compatible filters use successful processing time (processed_at); later revocation does not remove those successful-event counts. Queries needing labels, metrics, billing-account ownership or calendar-day analysis use occurrence time.

quantity and amount sum the selected metric quantities and amounts. item_count counts matching metric items; quantity_avg and amount_avg divide their totals by that count (zero when the count is zero). usage_event_count and event_count are aliases for event count. Filter to comparable data using metrics, labels or account ownership before combining different units or currencies.

Terminal window
curl 'http://127.0.0.1:8080/v1/subject/analysis/query' \
-H 'Authorization: Bearer <subject-token>' \
-H 'Content-Type: application/json' \
-d '{"start_time": 1781222400, "end_time": 1781308800, "metrics": ["input_tokens"], "measures": ["quantity", "amount"], "group_by": ["bucket", "model"], "limit": 100, "timezone": "UTC", "bucket_size": "1d"}'

200 OK

{
"group_by": [
"bucket",
"model"
],
"measures": [
"quantity",
"amount"
],
"series": [
{
"key": "<series-key>",
"label": "model=gpt-4.1-mini · Quantity",
"measure": "quantity",
"dimensions": {
"model": "gpt-4.1-mini"
},
"points": [
{
"bucket": "1781222400",
"value": "1200"
}
]
},
{
"key": "<amount-series-key>",
"label": "model=gpt-4.1-mini · Amount",
"measure": "amount",
"dimensions": {
"model": "gpt-4.1-mini"
},
"points": [
{
"bucket": "1781222400",
"value": "0.00024"
}
]
}
],
"meta": {
"timezone": "UTC",
"bucket_size": "1d",
"start_time": 1781222400,
"end_time": 1781308800
}
}
Field Contract
group_by Normalized grouping, including bucket.
measures Requested measure names.
series Array; empty if no series matches.
series[].key Stable series identifier.
series[].label Human-readable label.
series[].measure Measure represented by this series.
series[].dimensions Optional non-time grouping values.
series[].points Ordered {bucket,value} points; bucket is Unix-second string, value is decimal string. Missing buckets in an existing series are zero-filled.
meta Normalized timezone, bucket_size, original start_time and end_time.

Errors use {"error":"message"}.

401: invalid or expired token. 403: missing rule or subject mismatch. 400: invalid query. Query cancellation returns 408, timeout 504, and other query failures 500.