Skip to main content
Ramp Router is an OpenAI-Responses-compatible gateway in front of OpenAI, Anthropic and xAI, plus open models like DeepSeek, Qwen and Kimi served through Fireworks. Point an OpenAI client at it and wrap() detects it from the client’s base URL. No other code change. Two things about Router change how it bills. The model you asked for is not necessarily the model that answered, so the request is not a source of truth. And Router’s own rate card cannot be looked up, so there is nothing to price against.

Live path

Wrap an OpenAI client pointed at api.router.com. Bills as calls happen.

No backfill path

Router exposes no programmatic usage surface. Usage lives in its dashboard Logs view only.
There is no backfill for Router traffic. Every other gateway in this section pairs live instrumentation with a reader for the gateway’s own usage records. Router publishes none, so the live path is the whole integration. A call the wrapped client never saw cannot be recovered later.

Live path

There is no Ramp Router client to wrap. You wrap OpenAI pointed at Router, and the endpoint is what marks the call as Router-served.
Detection is by host, and by the parsed host rather than a substring: api.router.com, or any .router.com suffix for a regional or staging endpoint. A substring test would also match https://evil.example.com/api.router.com/v1 and stamp an unrelated endpoint’s traffic as Router-served. The base-URL path table that identifies Databricks and Snowflake still runs first, so those keep their own provider. Everything on the OpenAI page still applies: streaming, async, per-call overrides.

The served model is the one billed

Router does not always answer with the model you named. A models fallback list sends no model field at all, and Switchyard routing can substitute one model for another. So the SDK bills the model the response reports, which means Router-served traffic rolls up in Lago against the same model name a direct call would report. A fallback list replaces model rather than accompanying it. Sending both is a 400.
In JavaScript the fallback list goes on the request body unmodified.
Candidates are the catalog’s provider:model ids, the router.catalog_id on each GET /v1/models entry, not the id you would pass to model. A bare model name is rejected with models entry 0 must be a provider:model string.

Attribution

Attribution works as it does anywhere else in the SDK: subscription at wrap time, extra_lago={"subscription": "sub_acme"} per call in Python, or lago: { subscription: "sub_acme" } in TypeScript. Separately, put the same id in Router’s own metadata field. Router stores it with its usage record and shows it in the request detail.
Setting it costs nothing today. It is what a backfill would key off if Router publishes a usage API later, and it cannot be added retroactively to calls already made.
Streaming bills once, from the terminal usage event.

What gets captured

Events are stamped provider: "ramp_router", read from the Responses usage object: Audio and tool-call fields map as they do on any Responses call. See OpenAI for the full table.
There is no separate cache-write charge on Router. When a call writes to a provider’s prompt cache, those tokens are already counted inside input_tokens, so they arrive in your llm_input_tokens count and your plan’s input rate applies to them. Lago deliberately does not also emit llm_cache_creation_tokens for Router traffic, which would charge the same tokens twice.
Three keys land in extras:
extras is diagnostic and is not sent to Lago. These keys are visible to a debug or error hook, but they do not appear on your events and cannot be used to split a charge. To bill differently by service tier, set that up in your Lago plan.

Known limits

Everything bills as token counts, never priced. provider="ramp_router" is deliberately unmatchable against the price sources, because Router’s effective rate cannot be read from a response: a call served with your own provider key costs $0 through Router, and a non-default service tier bills at a rate Router’s own catalog says may differ from the one it publishes. You set the rate in Lago. Running pricing_mode="price" globally still gets token events here, with no price-miss error, because a structural absence of a rate card is not a lookup failure. This is why we recommend the OpenAI client: it is what lets Lago recognise the call as Router traffic and fall back to token counts instead of pricing it as something else. See Billing for the full reasoning. Only the OpenAI client is supported. Router also accepts Anthropic’s /v1/messages format, and pointing a wrapped Anthropic client at Router will look like it works, but Lago cannot tell those calls went through Router. It bills them as direct Anthropic usage at Anthropic’s public prices, which may be nothing like what Router charged you. If Router served the call with your own provider key, Router charged you nothing and Lago would still bill full price. Use the OpenAI client shown above. There is no backfill path. Router’s only routes are GET /v1/models, POST /v1/responses, POST /v1/messages and POST /v1/messages/count_tokens. Usage lives in the dashboard’s Logs view, which displays tokens, cost, service tier and metadata without offering any way to fetch them. An analytics API is referenced once in Router’s limits table with no path, no auth and no record shape, which is not enough to build against. Traffic the wrapped client never saw is not recoverable. Nothing is skipped as a gateway cache hit, because Router has no response cache. Provider prompt caching does pass through. Cache reads bill on llm_cached_input_tokens as they would on a direct call, and cache writes bill inside llm_input_tokens at the plain input rate. A proxy in front of Router is not detected. Reaching Router through your own hostname bills as plain OpenAI, with the wrong provider and an unparsed model id. Point the wrapped client at Router directly. api.router.com sits behind bot management. A rejected client can get an HTML challenge page rather than Router’s documented JSON error envelope. The SDK degrades to zero usage rather than throwing, either way. Failures never bill. Every documented Router status emits nothing, including 402 insufficient_credits, 429 rate_limit_exceeded and 502 all_candidates_failed, as does a response reporting zero usage.

Next steps

Billing

Why Router bills in tokens, and how to set the rate in Lago.

Configuration reference

Every config knob, in both SDKs.