API key

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

ParameterMeaning
time_rangeLookback window: <int><unit>, unit s, m, h or d (30s, 5m, 24h, 7d, 30d). Default 24h.
project_idOne project's id. Must be the caller's, else 404.
key_idOne 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": "paranine/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.

ParameterMeaning
time_range, project_id, key_id, modelAs above.
status_categoryExactly one of 2xx, 3xx, 4xx, 5xx. Any other non-blank value is 400.
termination_reasonExact match: success, proxy_guillotine (server-side timeout), client_disconnect, upstream_stream_aborted. An unknown value matches nothing.
min_cost_pkrMinimum settled cost, in PKR (0.01 excludes free error rows).
searchCase-insensitive match on the request id or on the X-P9-Meta-* metadata, nothing else.
limit, offsetPage 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": "paranine/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

FieldMeaning
metrics.cache_creation_input_tokensTokens written into the provider's prompt cache by this request.
metrics.cache_read_input_tokensTokens served out of that cache instead of being re-read as prompt.
metrics.total_tokensPrompt, 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_byok1 when the request was served against your own provider key from the vault, 0 when it was served on P/9 capacity. Sent as an integer, not a boolean.
user_agentThe User-Agent of the client that made the call, verbatim.

Both cache counts are null on P/9'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 P/9 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.