Control plane API
Analytics
Window summaries, time series, and the request-by-request log, all from the gateway's telemetry and scoped to your organisation.
The analytics endpoints read the gateway's request telemetry, which is a separate store from the control plane's own records. Every call is scoped to the caller's organisation from the session. If the telemetry store is unreachable they answer 503 {"error": "analytics backend unavailable"} while every other endpoint keeps working.
Common filters
| Parameter | Meaning |
|---|---|
time_range | Lookback window: <int><unit>, unit s, m, h or d (30s, 5m, 24h, 7d, 30d). Default 24h. |
project_id | One project's id. Must be the caller's, else 404. |
key_id | One key's id. Same ownership check. |
model_alias (model on /requests) | The route id the caller sent, not the resolved upstream model. |
Blank values equal omitted ones. Averages are null, not 0, over a window with no matching requests.
GET /analytics/summary
{
"time_range": "24h",
"total_requests": 1420,
"avg_e2e_latency_ms": 812.4,
"avg_ttft_ms": 143.2,
"avg_tokens_per_sec": 61.3,
"total_token_usage": 9412338,
"total_cost_pkr": 61.05
}time_range echoes the window applied; label panels from the response, not the request. total_cost_pkr (plain PKR) is present only when the query names a key_id; on an organisation-wide summary the field is absent. Tokens per second is derived at query time from tokens generated over generation time.
GET /analytics/timeseries
Parameters: metric (volume, latency, ttft, tok_s, token_usage), interval (bucket width, 30s, 5m, 1h, 1d), group_by (project, key, model), plus the common filters.
{
"metric": "latency",
"interval": "1h",
"group_by": "model",
"time_range": "24h",
"data": [
{"bucket": "2026-08-25T19:00:00", "group": "IntelligentInference/gpt-oss-120b(Global)", "value": 1200.0}
]
}One row per bucket and group, a flat shape you can pivot however your charting library wants. group is the raw project id, key id or route id; resolve names with /projects and /keys. Bucket timestamps carry no timezone and are UTC. The endpoint does not stop you pairing interval=30s with time_range=30d; pick a sane pairing.
GET /analytics/requests
The request log: one row per request, newest first, paged.
| Parameter | Meaning |
|---|---|
time_range, project_id, key_id, model | As above. |
status_category | Exactly one of 2xx, 3xx, 4xx, 5xx. Any other non-blank value is 400. |
termination_reason | Exact match: success, proxy_guillotine (server-side timeout), client_disconnect, upstream_stream_aborted. An unknown value matches nothing. |
min_cost_pkr | Minimum settled cost, in PKR (0.01 excludes free error rows). |
search | Case-insensitive match on the request id or on the X-I2-Meta-* metadata, nothing else. |
limit, offset | Page size (default 50, clamped to 200) and row offset. Use total to know when to stop. |
{
"total": 1420,
"limit": 50,
"offset": 0,
"requests": [
{
"req_id": "k2i-1787578888306946000-0001",
"timestamp": "2026-08-24T12:34:56.789Z",
"model": "IntelligentInference/gpt-oss-120b(Global)",
"status": 200,
"stream": true,
"key": {"id": "e02dcba3-…", "name": "checkout-summariser-prod"},
"project": {"id": "3dbb877d-…", "name": "Assistant"},
"metrics": {
"total_ms": 420,
"ttft_ms": 110,
"tpot_ms": 6.8,
"pre_dispatch_ms": 5,
"prompt_tokens": 45,
"tokens_generated": 40,
"cache_creation_input_tokens": null,
"cache_read_input_tokens": null,
"total_tokens": 85,
"tokens_estimated": false
},
"financials": {"cost_pkr": 0.04512, "is_byok": 0},
"status_detail": {"termination_reason": "success", "upstream_error": null},
"upstream": {"model": "openai/gpt-oss-120b"},
"user_agent": "OpenAI/Python 1.51.0",
"metadata": {"user_tag": "emp-4092", "feature_tag": "ticket_summary"}
}
]
}Nulls are meaningful: key or project is null when the request had no attribution at all (as opposed to an object whose name is null because the key has since been deleted); metrics.ttft_ms and tpot_ms are null for non-streaming requests and for a stream whose first-token event has not flushed yet; metrics.prompt_tokens and the whole upstream block are null on rows served before 2026-08-24, when the gateway began recording them.
Prompt caching, billing mode and client
| Field | Meaning |
|---|---|
metrics.cache_creation_input_tokens | Tokens written into the provider's prompt cache by this request. |
metrics.cache_read_input_tokens | Tokens served out of that cache instead of being re-read as prompt. |
metrics.total_tokens | Prompt, completion and both cache legs. A request that read a large cache has a total far above prompt_tokens + tokens_generated; do not re-derive the total by adding those two. |
financials.is_byok | 1 when the request was served against your own provider key from the vault, 0 when it was served on i2 capacity. Sent as an integer, not a boolean. |
user_agent | The User-Agent of the client that made the call, verbatim. |
Both cache counts are null on i2's own global routes, which have no prompt cache. null there means the route does not cache, which is not the same claim as 0, and a dashboard or report that treats them alike will show a route with no cache as a route that never hits one.
is_byok is the explanation for a near-zero cost_pkr. On a BYOK request the provider bills you directly and i2 settles only its own share, so a row can legitimately cost a fraction of a paisa. Read the cost against this flag, not on its own.
Something unclear or wrong on this page? Tell us. Machine-readable copies: llms.txt.