Gateway
Metering & cost
Every request is priced at the moment it is served and the arithmetic comes back on the response. Where the honest answer is null, the header is empty rather than zero.
Two things are written for every request that reaches the Gateway. A priced ledger row in token_usage, which carries the price that was applied at request time, and a trace row in gateway_requests, which carries the status code, the latency and the failure class. Both are best-effort by design: neither may fail a completion a provider has already charged for.
The response headers
Routing
| Header | Value | Notes |
|---|---|---|
| x-lobstack-request-id | UUID | The trace row id. Allocated before anything can fail, so a 401 carries one too. |
| x-lobstack-model | model key | What actually served the request. Not necessarily what you sent. |
| x-lobstack-tier | nano … flagship | The tier the score landed in. |
| x-lobstack-complexity | 0–100 | The heuristic score for the last user message. |
| x-lobstack-routed | true | false | True whenever you sent "auto", and whenever the served model differs from the one you named. |
| x-lobstack-mode | managed | byok | Whose provider key paid for the tokens. |
| x-lobstack-principal | api_key | gateway_token | agent_secret | Which credential authenticated the call. |
| x-lobstack-dropped-params | temperature | Present only when a sampling parameter you sent was not forwarded. |
Cost
| Header | Value | Notes |
|---|---|---|
| x-lobstack-cost-usd | USD, 6 decimals | What you owe for these tokens. Empty string when the model is unpriced. |
| x-lobstack-savings-usd | USD, 6 decimals | The baseline cost minus the cost above. Empty string when there is no baseline. |
| x-lobstack-baseline-model | model key | The model the saving was measured against. Sent only when a baseline exists. |
| x-lobstack-baseline-reason | named | plan_ceiling | Why that model is the comparison. Read it before rendering the saving. |
| x-lobstack-baseline-usd | USD, 6 decimals | What the baseline model would have charged for these exact token counts. |
| x-lobstack-savings-pct | 0–100 | Tier-multiplier estimate, not dollars. See Token Intelligence. |
| x-lobstack-priced | true | false | False when the served model is not in the registry, so cost is null rather than zero. |
| x-lobstack-metered | true | false | False when the ledger write failed. You still got your answer; the row is missing. |
provider_cost_usd and stays there: it appears in no header, no stream frame and no API response.Quota
Three meters exist, because three different things are sold, and the counters mean different things on each. x-lobstack-quota-meter names the one that applied, and it is the header to branch on before reading any of the others.
| Meter | Who is on it | What the wall is |
|---|---|---|
| spend | A plan that includes dollars of model spend | Included allowance, plus purchased top-ups, plus credited savings, minus this period's spend |
| requests | BYOK — you pay your own provider, so there is no spend of ours to cap | Included requests for the calendar month |
| legacy | A subscription bought on the older messages-per-month tiers | That subscription's own message allowance, counted exactly as it always was |
| Header | Value | Sent on |
|---|---|---|
| x-lobstack-quota-meter | spend | requests | legacy | Every response with anything to measure against. |
| x-lobstack-quota-allowance-usd | USD, 6 decimals | spend. Included, plus top-ups, plus credited savings. |
| x-lobstack-quota-spent-usd | USD, 6 decimals | spend. Consumed so far this period. |
| x-lobstack-quota-remaining-usd | USD, 6 decimals | spend. Never negative. |
| x-lobstack-quota-limit | integer | requests and legacy. Included requests or messages. |
| x-lobstack-quota-used | integer | requests and legacy. Consumed so far. |
| x-lobstack-quota-remaining | integer | requests and legacy. Limit minus used, plus credits. Never negative. |
| x-lobstack-quota-credits | integer | requests and legacy, and only when greater than zero. |
| x-lobstack-quota-resets | ISO 8601 | Any meter, when the period end is known. |
A spend meter sends dollars and no request counters. A zero in x-lobstack-quota-limit reads as "no allowance" to a client that cannot tell which meter it is on, so the counters are absent rather than zero. The rule runs the other way too: a legacy subscriber who bought messages is never sent a dollar figure they did not agree to be measured in.
These are advisory for agent credentials and enforced for API keys. Errors & retries covers the 402 and the retry-after that comes with it. All of them are omitted when there is nothing to measure against — no agent row and no org.
Only requests that returned below 400 count against a request or legacy org allowance, so a provider outage on our side does not consume your quota.
stream: true, because headers flush before the provider has reported a single token. The price arrives instead on the final SSE chunk, under x_lobstack, next to the usual usage object — see below. Routing and quota headers are present on both paths.Cost on a streamed response
The last chunk before [DONE] carries token counts in the standard usage object and the money beside it under x_lobstack. An SDK that does not know us parses the usage object unchanged and ignores the rest.
Read the price from here rather than pricing the token counts against your own copy of our rate card. A local copy drifts: we serve 26 models today and retire or reprice some of them every few weeks, and a client that cannot price the model our router chose will show $0.00 for a request that is on your bill.
cost_usd is null, never 0, when the served model is not in the registry or the stream ended before it could be metered — priced tells you which case you are in. Render a null as unknown; rendering it as free writes off a real charge.
The three baseline fields are sent together or not at all, and savings_usd never travels without baseline_reason. What the two reasons mean, and why they are not interchangeable, is the next section.
The baseline rule
A saving is a subtraction, and a subtraction is honest only when the reader can see both operands and where the first one came from. So a baseline never travels without the reason it exists. The saving is always what these exact token counts would have cost on the baseline model, minus what they actually cost, priced on the same basis so the subtraction is between two comparable numbers.
| baseline_reason | You sent | The baseline is | What the saving means |
|---|---|---|---|
| named | An explicit model key | The model you named | A measurement against your own request. |
| plan_ceiling | auto | The most expensive managed model your plan may reach | What you would have paid had you asked for the best one. Nobody asked for it. |
| null | Anything with no honest comparison | None | No saving is reported and savings-usd is empty. |
named compares against a model the caller chose. plan_ceiling compares against one they never mentioned, chosen because it is the most expensive their plan can reach — which is also the most flattering number available to us. Render baseline_reason next to the figure, or do not render the figure. A client that shows a plan-ceiling saving as though the customer had asked for that model is publishing a number they never chose to be measured against.| You sent | Served | baseline_reason | savings-usd |
|---|---|---|---|
| auto | a cheaper model, on a priced plan | plan_ceiling | the difference against the plan's ceiling model |
| auto | the plan's ceiling model itself | plan_ceiling | 0.000000 — a real zero, not a null |
| auto | anything, on a legacy messages tier | null | empty — that contract has no ceiling to compare against |
| claude-opus-5 | gemini-3.1-flash-lite | named | the measured difference |
| claude-opus-5 | claude-opus-5 | null | empty — nothing was routed away from |
| a key not in the registry | the fallback model | null | empty — no price for the baseline |
x-lobstack-savings-pct follows a different rule and is present on every response. It is one minus the selected tier's cost multiplier over the ceiling tier's: an estimate about tiers, not a measurement about dollars. It is not a rounded version of the dollar figure and the two should never be added together.
Savings credit
On a plan that carries it, 50% of a verified saving is added back to that period's allowance. Verified is the whole of the rule, and every one of these must hold or the credit is zero.
| Condition | Why |
|---|---|
| The plan credits savings, and meters spend | There is no dollar allowance to extend otherwise. |
| The request ran on our provider key | On BYOK you paid your own provider; there is nothing of ours to give back. |
| The row is not an error row | A failed round saved nobody anything. |
| Both models priced | A credit computed from a null is an allowance handed out by a gap in the catalogue. |
| The baseline is strictly above the actual cost | Equal or below is not a saving. |
Routing does not always find something cheaper, and on the calls where it does not there is no saving and no credit. Nothing here promises a saving of any size; the credit is a share of whatever is actually measured, which is sometimes nothing.
The credit is computed on every qualifying request, reported in the meter result, and accrued into spend_allowances — one row per payer per billing period — through an atomic SQL function, so concurrent requests cannot lose a credit to a read-modify-write. The row survives plan changes and subscription churn, because a balance the customer earned should not be stranded on a cancelled subscription mid-period. What each plan credits, and which plans meter spend at all, is on Pricing & plans.
What a ledger row records
One row per inference round in token_usage, including rounds that failed with zero tokens, so an error rate stays computable from the ledger alone.
| Column | What it holds |
|---|---|
| prompt_tokens, completion_tokens, total_tokens | The provider's own counts. |
| model | The Lobstack key that served the request. |
| provider | Resolved from the registry. Null when the model was unpriced. |
| cost_usd | What the customer owes, frozen at write time. Null when unpriced. |
| provider_cost_usd | What we paid the provider for the same tokens. Server-side only; never returned. |
| rate_multiplier | The spread applied to reach cost_usd, so the row reproduces its own arithmetic. |
| price_in_per_m, price_out_per_m | The provider's per-million list rates that were in force. |
| requested_model | What you asked for — "auto", or a key. |
| routed | True when the served model differs from the requested one. |
| baseline_cost_usd | What the baseline model would have charged. Null when there is no baseline. |
| baseline_model, baseline_reason | Which model, and why it was the comparison. |
| savings_credit_usd | The share of a verified saving credited back to the allowance. Zero when not credited. |
| plan_id | The priced plan in force. Null on a legacy tier. |
| mode | managed or byok — who paid the provider. Only managed is billable to us. |
| source | Which surface produced it: gateway, chat, messages or bridge. |
| request_id, session_id | Links to the trace row, and your own grouping key. |
| agent_id, org_id, api_key_id | Whose spend this was. agent_id is null for an org-level key. |
| latency_ms, error | Round-trip time and whether the round failed. |
The row carrying its own price is the point. Editing the model registry later cannot retroactively move a historical invoice, because nothing recomputes an old row from the current catalog.
provider_cost_usd through plan_id arrived with 20260910_spend_metering.sql, which is applied: every column above exists and is written today. Rows written before it carry a customer cost and no margin columns, and rows older than the priced ledger carry no cost at all. Nothing backfills them, because the prices and the plan in force at the time are not recoverable — the gaps stay visible as gaps rather than being filled in with a number nobody can reconstruct. The Gateway still detects a missing column and writes the row without it rather than dropping the row, so a deployment on an older schema loses the margin columns and not the ledger.Unpriced requests
When the served model key is not in the registry, the price is recorded as null, not zero. A missing price is a gap in the catalog; recording it as free would silently write off real spend, which is the most expensive way to be wrong here. The response reports x-lobstack-priced: false and the header value for cost is empty.
Every one of these is logged loudly on the server and counted in the usage rollup as an unpriced request. If you see one, the fix is a registry entry or an alias, not a default. This is also why aliases resolve for pricing and not only for routing: a live agent once ran on a spelling the resolver did not know, and every request it made metered free.
The usage API
Org-scoped usage, traces and latency. Two credentials work: a browser session, which is what the Console reads, and an API key carrying the usage:read scope. Both get identical numbers from identical arithmetic rather than each computing their own.
Query parameters: range (7d, 14d, 30d, 90d; default 7d), group_by (day, model, key, agent; default day), agent_id and key_id.
Percentiles are true percentiles over the raw latency values, not an average of per-bucket averages — which is the usual way a dashboard ends up reporting a p95 nobody experienced.
truncated is true when the row cap bound, at which point the sums are a floor rather than a total and the response says so instead of handing you a confident wrong number. Narrow the range or filter by key.
enabled: false with reason: "request_tracing_not_migrated" rather than a 500 that reads like an outage. An API key without the usage:read scope gets a 403 naming the missing scope.