Console
API monitor
Every request that reached the Gateway, including the ones that failed before a token existed. Counts, percentiles and a failure breakdown, over real rows.
/dashboard/api answers one question: is the API working. The priced ledger could already tell you what you spent, but a 502 leaves no tokens behind, so spend alone cannot see an outage. This surface reads the request trace table instead, where a row is written for every request whatever its outcome.
Every figure on the page is a count or a percentile over those rows. Nothing is modelled, scored or estimated. Where a number would have to be invented, it is not shown.
The headline row
| Metric | What it is |
|---|---|
| Requests | Rows in the window. The sub-line counts how many were streamed. |
| Error rate | Rows with a status code of 400 or above, over total rows. Green to 1%, amber to 5%, red beyond. |
| p50 latency | The median of every recorded latency in the window. |
| p95 latency | The 95th percentile. Its sub-line carries p99. |
| Cost | Sum of the per-request cost recorded at request time, with total tokens beneath. |
The percentiles are computed over the raw latency values, sorted, not as an average of per-bucket averages. That distinction matters: averaging buckets produces a p95 nobody ever experienced.
The failure taxonomy
When there are errors in the window, they are broken into six classes, sorted by count, each with a share bar. The taxonomy is deliberately small: six buckets that each imply a different response, rather than a long list nobody can group by.
| Class | What it means | Whose move |
|---|---|---|
| auth | 401 or 403 — the credential was rejected. | The caller fixes its key. |
| quota | 402 or 429 — the allowance is exhausted. | Upgrade, top up, or wait for the reset. |
| validation | 400, 409 or 422 — the request was malformed. | The caller fixes the call. |
| provider | A 5xx from upstream that is not our own 500. | Retry. Not the caller's fault. |
| timeout | 504 or 408 — we gave up waiting. | Retry. |
| internal | 500, and anything thrown that we could not classify. | Ours. Quote the request id. |
An unknown throw is classified as internal, not provider. Blaming upstream by default would quietly hide our own crashes. A seventh label, unclassified, appears only on rows written before the taxonomy existed.
Filters and breakdowns
The window is 7, 14, 30 or 90 days. The breakdown below it groups the same rows four ways:
| Grouping | Key | Use it for |
|---|---|---|
| Over time | Calendar day (UTC) | Did something change, and when. |
| By model | The model that served the request | Which model is slow, or expensive, or failing. |
| By key | The API key that authenticated it | Which client caused this spike. |
| By runtime | The legacy agent row the request was attributed to, or none | Separating traffic from a legacy agent credential from traffic on an API key. |
Each row carries requests, errors, tokens, cost, p50 and p95 for that group alone. Under Over time a stacked bar chart sits above the table, successes at the baseline and failures on top, with the peak day's request count stated in the footer rather than the axis maximum.
One more filter appears when a runtime is selected in the sidebar: a This runtime only toggle. Off, the page covers the whole organization, which is the default because an organization is the unit a bill and an incident belong to.
What a trace row contains
A trace row is written for every request that reaches the Gateway, before it is known whether the request will succeed. Its id is allocated up front so it can be returned even when everything else fails.
| Field | Note |
|---|---|
| id | The request id. Returned to the caller and quotable in support. |
| org_id, api_key_id, agent_id | Who the request belonged to. Any of the last two may be null. |
| route | Which Gateway route handled it, e.g. chat.completions. |
| requested_model, served_model, routed | What was asked for, what answered, and whether those differ. |
| provider | The upstream provider that served it. |
| status_code | The HTTP status returned to the caller. |
| error_class, error_code, error_message | The bucket, the provider's code, and the message truncated to 1,000 characters. |
| latency_ms, ttft_ms | Total latency, and time to first token on streamed requests. |
| prompt_tokens, completion_tokens, total_tokens | Zero on requests that failed before inference. |
| cost_usd | Priced at request time. Null means unpriced, which is not the same as free. |
| mode | managed (our provider key) or byok (yours). |
| stream | Whether the caller asked for server-sent events. |
| client | The caller's self-reported X-Lobstack-Client or user agent, truncated to 200 characters. A label, never an authorization input. |
How a row maps to the headers the caller saw
The trace row and the response headers are written from the same request, so an operator and a caller are describing the same event. If somebody quotes you a header value, this is the column it landed in.
| Response header | Trace column |
|---|---|
x-lobstack-request-id | id |
x-lobstack-model | served_model |
x-lobstack-routed | routed |
x-lobstack-mode | mode |
x-lobstack-cost-usd | cost_usd |
x-lobstack-tier | Not stored. The tier is on the response only; the trace keeps the model, not the tier it came from. |
The request id is on successful responses, on error responses, and inside the error body. It is the fastest route from “something went wrong at 14:02” to the row that says what.
The same numbers, programmatically
The panel is a client of GET /api/v1/usage and adds no arithmetic of its own. A key with the usage:read scope reads the identical response, so a script and the Console can never disagree.
The panel refreshes itself every 30 seconds. Traces are written on every request, so this stays close to live without polling a table that is scanned rather than indexed by minute. More on the credential in API keys.