Console
API reference

Usage and generations

Spend, balance and the record of each request.

GET/v1/usage

Two 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:

bash
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"
Note

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.

bash
curl -s https://dev2-api.routerplus.com/v1/usage -H "Authorization: Bearer $TM_API_KEY" | jq
json
{
  "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).

FieldTypeMeaning
balance_usdnumber | nullcredited_usd − spent_usd, computed from ledger rows on each read. Never a cached counter.
credited_usdnumber | nullThe sum of all credit grants to the org.
spent_usdnumber | nullThe sum of every settled attempt.
recent_attempts[].request_idstringThe request's x-request-id. Pass it to /v1/generation.
recent_attempts[].deploymentstringThe 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[].modelstringThe model id you requested (for an rp- name, the Claude model's own id). On a closed dedicated endpoint, the endpoint id.
recent_attempts[].outcomestringSee Attempt outcomes.
recent_attempts[].usage_provenancestring | nullobserved, estimated or unknown. null before settlement.
recent_attempts[].input_tokens / output_tokensnumberProvider-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_usdnumber | nullThe settled cost, any web search fee included. null until settlement is written (in batches, about every 100 ms).
recent_attempts[].server_tool_useobject | nullThe 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_usdnumber | nullThe part of cost_usd that is web search fees (see Pricing & billing).
recent_attempts[].atstringThe 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:

FieldMeaning
billing_sourcehouse for inference paid from RouterPlus credits. byok for inference on your own provider connection.
inference_cost_usdThe cost from the pinned rates and the usage. null when either is unknown.
inference_price_sourceplatform_list, customer_rate, unknown, or legacy_platform_list for old records.
price_snapshot_idThe id of the pinned rate snapshot. null when unknown or not kept for old records.
fee_policy_versionThe pricing policy for this attempt: house-pass-through-v1 or byok-free-v1.
  • cost_usd is the RouterPlus charge. Spend caps apply to it.
  • inference_cost_usd is 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.

ParameterInRequiredMeaning
idqueryyesThe request id: the x-request-id header on every response, or request_id in /v1/usage rows.
  • An id that is not a UUID returns 404 not_found.
  • An unknown id, or one from another org, workspace or principal, returns 404 with "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.

bash
curl -s "https://demo-api.routerplus.com/v1/generation?id=0b6f6a4e-6a01-4c5e-9d5a-1c9f2b7e8a33" \
  -H "Authorization: Bearer $TM_API_KEY" | jq
json
{
  "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"
    }
  ]
}
FieldTypeMeaning
attemptnumberThe dispatch number, starting at 1.
deploymentstringThe deployment that served this attempt. routerplus on a closed dedicated endpoint.
outcomestringSee Attempt outcomes.
usage_provenancestringobserved (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.
tokensobjectinput (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_usdnumberThe worst-case hold taken before dispatch (ceiling math).
cost_usdnumber | nullWhat settled (floor math), any web search fee included. null only before settlement.
server_tool_useobject | nullweb_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_usdnumber | nullThe part of cost_usd that is web search fees.
error_codestringThe error class on failure. Empty on success.
error_originstring | nullWhere a failure came from: upstream, gateway_admission, upstream_quota, gateway_infrastructure or authorization. null on success.
admission_contextobject | nullThe limits this attempt claimed, recorded at dispatch. See Limits and capacity.
route_contextobject | nullThe 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_paramsarrayThe 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:

  • deployment and provider are routerplus, and model is the endpoint id.
  • route_context keeps only dedicated, admission_context is null and dropped_params is empty.
  • admission_events carry no pool scope_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

outcomeMeaningBilling
completedThe full response was delivered.Settled on reported usage.
failedThe attempt failed. A later attempt may have served you.Settled on any usage the provider reported before it failed (often 0).
cancelledYou 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.
incompleteThe 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_crashThe 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.
Warning

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:

bash
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:

python
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_crash attempts. They settle at the reservation, not from token counts. Compare their reserved_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, and output is the charge in micro-dollars.
  • An attempt with inference_price_source customer_rate was 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 null for 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:

Console Analytics page, grouped by model and then by provider

For agents: /docs/api-usage.md and /llms.txt