Subject Analysis Query
Return chart-ready time series for the token subject.
POST /v1/subject/analysis/queryAuthentication
Section titled “Authentication”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/jsonOmit 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.
Request Body
Section titled “Request Body”| 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.
Example
Section titled “Example”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"}'Response
Section titled “Response”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
Section titled “Errors”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.