Usage and generations
Spend, balance and the record of each request.
/v1/usageTwo read-only gateway endpoints report your spend. GET /v1/usage returns your balance and recent attempts. GET /v1/generation returns the cost of one request, attempt by attempt. Both read the ledger that your balance comes from, so they match your bill.
Both return metadata only: token counts, outcomes and costs. They never return prompts or completions. See the Data policy.
In the console, open Usage or Logs for the same data across your organization.
Authentication
Both endpoints need a billed key: the first key shown after verified sign-in, or one created in the console. Results cover that key's org, workspace and principal. Send the key in either header:
curl -s https://dev2-api.routerplus.com/v1/usage -H "Authorization: Bearer $TM_API_KEY"
curl -s https://dev2-api.routerplus.com/v1/usage -H "x-api-key: $TM_API_KEY"A static development key (a gateway running without the money plane) has no ledger to read. On these routes it gets the same 404 as any unknown path.
GET /v1/usage
Returns the 20 most recent attempts made with the keys of your workspace and principal, newest first.
curl -s https://dev2-api.routerplus.com/v1/usage -H "Authorization: Bearer $TM_API_KEY" | jq{
"balance_usd": 0.98971,
"credited_usd": 1.0,
"spent_usd": 0.01029,
"recent_attempts": [
{
"request_id": "0b6f6a4e-6a01-4c5e-9d5a-1c9f2b7e8a33",
"deployment": "provider-b",
"model": "claude-sonnet-4-5",
"outcome": "completed",
"usage_provenance": "observed",
"input_tokens": 1200,
"output_tokens": 350,
"cost_usd": 0.00669,
"billing_source": "house",
"inference_cost_usd": 0.00669,
"inference_price_source": "platform_list",
"price_snapshot_id": "5d2c…",
"fee_policy_version": "house-pass-through-v1",
"at": "2026-09-04T09:12:33.104Z"
},
{
"request_id": "0b6f6a4e-6a01-4c5e-9d5a-1c9f2b7e8a33",
"deployment": "provider-a",
"model": "claude-sonnet-4-5",
"outcome": "failed",
"usage_provenance": "observed",
"input_tokens": 1200,
"output_tokens": 0,
"cost_usd": 0.0036,
"billing_source": "house",
"inference_cost_usd": 0.0036,
"inference_price_source": "platform_list",
"price_snapshot_id": "5d2c…",
"fee_policy_version": "house-pass-through-v1",
"at": "2026-09-04T09:12:31.512Z"
}
]
}The org balance fields are filled for a key in the organization's default workspace and default principal. Every first key shown after verified sign-in, and every key made in the console, is one. A key bound to an explicit workspace or principal gets null (see Workspaces and principals).
| Field | Type | Meaning |
|---|---|---|
balance_usd | number | null | credited_usd − spent_usd, computed from ledger rows on each read. Never a cached counter. |
credited_usd | number | null | The sum of all credit grants to the org. |
spent_usd | number | null | The sum of every settled attempt. |
recent_attempts[].request_id | string | The request's x-request-id. Pass it to /v1/generation. |
recent_attempts[].deployment | string | The deployment that served the attempt. It matches the x-tm-provider response header. It is routerplus on a closed dedicated endpoint and on any endpoint's dedicated capacity. |
recent_attempts[].model | string | The model id you requested (for an rp- name, the Claude model's own id). On a closed dedicated endpoint, the endpoint id. |
recent_attempts[].outcome | string | See Attempt outcomes. |
recent_attempts[].usage_provenance | string | null | observed, estimated or unknown. null before settlement. |
recent_attempts[].input_tokens / output_tokens | number | Provider-reported counts. Input includes cache tokens. On a model billed at the provider's reported cost, output_tokens is the charge in micro-dollars. |
recent_attempts[].cost_usd | number | null | The settled cost, any web search fee included. null until settlement is written (in batches, about every 100 ms). |
recent_attempts[].server_tool_use | object | null | The web searches and web fetches the provider ran for the model: web_search_requests, web_fetch_requests. null on attempts from before these were recorded. |
recent_attempts[].server_tool_fee_usd | number | null | The part of cost_usd that is web search fees (see Pricing & billing). |
recent_attempts[].at | string | The dispatch time. |
Each row is one attempt. A request that failed over has one row per dispatch, all with the same request_id.
Accounting fields
Both endpoints return these fields on each attempt:
| Field | Meaning |
|---|---|
billing_source | house for inference paid from RouterPlus credits. byok for inference on your own provider connection. |
inference_cost_usd | The cost from the pinned rates and the usage. null when either is unknown. |
inference_price_source | platform_list, customer_rate, unknown, or legacy_platform_list for old records. |
price_snapshot_id | The id of the pinned rate snapshot. null when unknown or not kept for old records. |
fee_policy_version | The pricing policy for this attempt: house-pass-through-v1 or byok-free-v1. |
cost_usdis the RouterPlus charge. Spend caps apply to it.inference_cost_usdis a separate cost estimate, not a provider invoice. A list-price or estimated-usage figure can differ from your provider bill.- BYOK attempts have a RouterPlus charge of zero and an unknown (
null) inference cost. See Bring your own key and routing policies.
GET /v1/generation?id=
Returns the full per-attempt audit for one request.
| Parameter | In | Required | Meaning |
|---|---|---|---|
id | query | yes | The request id: the x-request-id header on every response, or request_id in /v1/usage rows. |
- An
idthat is not a UUID returns404 not_found. - An unknown id, or one from another org, workspace or principal, returns
404with"attempts": []and"admission_events": [].
This example is a request that failed over once. The deployment ids are examples. Yours match your x-tm-provider headers.
curl -s "https://demo-api.routerplus.com/v1/generation?id=0b6f6a4e-6a01-4c5e-9d5a-1c9f2b7e8a33" \
-H "Authorization: Bearer $TM_API_KEY" | jq{
"request_id": "0b6f6a4e-6a01-4c5e-9d5a-1c9f2b7e8a33",
"admission_events": [],
"attempts": [
{
"attempt": 1,
"deployment": "provider-a",
"model": "claude-sonnet-4-5",
"outcome": "failed",
"usage_provenance": "observed",
"tokens": { "input": 1200, "cached": 0, "cache_write": 0, "output": 0, "reasoning": 0 },
"reserved_max_usd": 0.009,
"cost_usd": 0.0036,
"billing_source": "house",
"inference_cost_usd": 0.0036,
"inference_price_source": "platform_list",
"price_snapshot_id": "5d2c…",
"fee_policy_version": "house-pass-through-v1",
"error_code": "upstream_error",
"error_origin": "upstream",
"admission_context": { "…": "…" },
"route_context": { "…": "…" },
"dropped_params": [],
"dispatched_at": "2026-09-04T09:12:31.512Z"
},
{
"attempt": 2,
"deployment": "provider-b",
"model": "claude-sonnet-4-5",
"outcome": "completed",
"usage_provenance": "observed",
"tokens": { "input": 1200, "cached": 800, "cache_write": 0, "output": 350, "reasoning": 0 },
"reserved_max_usd": 0.009,
"cost_usd": 0.00669,
"billing_source": "house",
"inference_cost_usd": 0.00669,
"inference_price_source": "platform_list",
"price_snapshot_id": "5d2c…",
"fee_policy_version": "house-pass-through-v1",
"error_code": "",
"error_origin": null,
"admission_context": { "…": "…" },
"route_context": { "…": "…" },
"dropped_params": [],
"dispatched_at": "2026-09-04T09:12:33.104Z"
}
]
}| Field | Type | Meaning |
|---|---|---|
attempt | number | The dispatch number, starting at 1. |
deployment | string | The deployment that served this attempt. routerplus on a closed dedicated endpoint. |
outcome | string | See Attempt outcomes. |
usage_provenance | string | observed (provider-reported tokens, or a completed attempt), unknown (no usage seen), or estimated: the provider reported nothing and the gateway priced the attempt itself. On /v1/images/generations, a 200 without usage bills n × the model's per-image ceiling, the amount the reservation held. On a chat attempt cancelled or cut off before usage arrived, it bills the input (the gateway's estimate, when the provider sent no count) plus the output streamed to you. |
tokens | object | input (includes cache tokens), cached (reads), cache_write, output, reasoning (part of output). On a model billed at the provider's reported cost, output is the charge in micro-dollars. |
reserved_max_usd | number | The worst-case hold taken before dispatch (ceiling math). |
cost_usd | number | null | What settled (floor math), any web search fee included. null only before settlement. |
server_tool_use | object | null | web_search_requests and web_fetch_requests: the server-tool calls the provider ran for the model. null on attempts from before these were recorded. |
server_tool_fee_usd | number | null | The part of cost_usd that is web search fees. |
error_code | string | The error class on failure. Empty on success. |
error_origin | string | null | Where a failure came from: upstream, gateway_admission, upstream_quota, gateway_infrastructure or authorization. null on success. |
admission_context | object | null | The limits this attempt claimed, recorded at dispatch. See Limits and capacity. |
route_context | object | null | The route plan behind this attempt: plan id, the deployments planned and excluded, and the policy revisions in force. On a dedicated endpoint, dedicated (the endpoint, its revision and the role). |
dropped_params | array | The parameter paths the gateway dropped for this deployment, with the same names as the x-tm-dropped-params header. |
admission_events lists the limit or balance refusals recorded for this request before any attempt (origin, scope, scope_id, limit_kind, reason, status, occurred_at). It is empty when nothing was refused.
On a closed dedicated endpoint:
deploymentandproviderarerouterplus, andmodelis the endpoint id.route_contextkeeps onlydedicated,admission_contextis null anddropped_paramsis empty.admission_eventscarry no poolscope_id.
On any dedicated endpoint, its dedicated capacity reads as routerplus.
reserved_max_usd is the hold, released in full at settlement. cost_usd is the settled amount, from provider-reported usage, rounded down. See Pricing & billing.
Attempt outcomes
outcome | Meaning | Billing |
|---|---|---|
completed | The full response was delivered. | Settled on reported usage. |
failed | The attempt failed. A later attempt may have served you. | Settled on any usage the provider reported before it failed (often 0). |
cancelled | You disconnected mid-request. | Settled on the usage the provider reported up to the disconnect. When it reported none: the input plus the output streamed to you before the disconnect, never the hold. If you disconnected before the attempt left the gateway, it settles at zero with provenance observed. On /v1/images/generations the render is never aborted: it finishes upstream and the attempt settles the provider's reported usage. |
incomplete | The provider failed after your stream started. You received a terminal error event. | Settled on usage observed so far. When there was none: the input plus the output streamed to you. |
unknown_after_crash | The gateway stopped mid-attempt and could not observe the outcome. The attempt is recovered at restart from the gateway's ledger journal, or by the operator reconciler. | Settles at the reservation, because the provider may have billed the call. |
You are debited for every settled attempt of a request, including ones discarded by failover before your answer started. A provider that fails after reading your prompt has still billed prompt tokens. The response's usage.cost is the request total. In the example above it was "cost": 0.01029: 0.0036 + 0.00669, the sum of both attempts.
Recomputing your bill
The sum of attempt costs equals the response's usage.cost:
curl -s "https://demo-api.routerplus.com/v1/generation?id=$REQ" -H "Authorization: Bearer $TM_API_KEY" \
| jq '[.attempts[].cost_usd] | add'You can recompute each attempt's cost from its token counts and the published prices. This script uses the settlement math, including floor division:
import os
import requests
from decimal import Decimal
GATEWAY = "https://demo-api.routerplus.com"
H = {"Authorization": f"Bearer {os.environ['TM_API_KEY']}"}
prices = {m["id"]: m["pricing_usd_per_million"]
for m in requests.get("https://demo.routerplus.com/api/models.json").json()["models"]}
def per_m(p, sku, fallback):
# absent SKUs bill at the base rate, never $0
return int(Decimal(p.get(sku, p[fallback])) * 1_000_000) # micro-USD per M
def settle_micro(t, p):
non_cached = max(0, t["input"] - t["cached"] - t["cache_write"])
reasoning = min(max(0, t["reasoning"]), t["output"])
plain_out = t["output"] - reasoning
total = (non_cached * per_m(p, "prompt", "prompt")
+ t["cached"] * per_m(p, "cached_prompt", "prompt")
+ t["cache_write"] * per_m(p, "cache_write", "prompt")
+ plain_out * per_m(p, "completion", "completion")
+ reasoning * per_m(p, "internal_reasoning", "completion"))
return total // 1_000_000 # floor: settlement rounds down
gen = requests.get(f"{GATEWAY}/v1/generation",
params={"id": os.environ["REQ"]}, headers=H).json()
micro = sum(settle_micro(a["tokens"], prices[a["model"]])
for a in gen["attempts"] if a["outcome"] != "unknown_after_crash")
print(micro / 1e6) # equals the wire's usage.cost- The script leaves out
unknown_after_crashattempts. They settle at the reservation, not from token counts. Compare theirreserved_max_usd. - The same math holds for a model billed at the provider's reported cost. Its prices are
"0"in and"1"out per million, andoutputis the charge in micro-dollars. - An attempt with
inference_price_sourcecustomer_ratewas billed at a dedicated endpoint's contract rate card. Recompute it with those prices. - Attempts settle against frozen price snapshots pinned when the request was reserved. A recomputation at today's catalog differs only if the price changed since dispatch.
Who can read what
- Usage and generation reads cover the authenticated workspace and principal, including that principal's other keys.
- A request made under another workspace or principal is a 404, like one that never existed.
- Keys bound to an explicit workspace or principal get
nullfor the org balance, credit and spend totals. A key in the default workspace and principal gets them.
Members of the organization see the org-wide accounting in the console at https://demo.routerplus.com/console/usage and https://demo.routerplus.com/console/logs.
In the console, open Analytics for spend, requests and tokens by model, provider, workspace, member or key:

Related
- Pricing & billing: the reserve and settle math behind these numbers
- Errors & remediation: what each
error_codemeans and what to do - Quickstart: signup to first billed call
For agents: /docs/api-usage.md and /llms.txt