Skip to main content
Ramp Router is a gateway in front of OpenAI, Anthropic and xAI, plus open models such as DeepSeek, Qwen, Kimi and GLM served through Fireworks and Baseten. It accepts OpenAI’s Responses format and Anthropic’s Messages format. Point an OpenAI or Anthropic client at it and wrap() detects Router from the client’s base URL. No other code change. Two things about Router shape how it bills. The model you asked for is not necessarily the model that answered, so the SDK bills the one the response reports. And Router bills its own rate card, published per model in GET /v1/models for your account, so price mode reads that catalog rather than a public listing.

Live path

Wrap an OpenAI or Anthropic client pointed at api.router.com. Bills as calls happen, priced from Router’s own catalog.

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

Router has no client of its own. You wrap an OpenAI or Anthropic client pointed at Router, and the endpoint is what marks the call as Router-served. Use the Anthropic client for Anthropic models. Router’s Messages surface reports prompt-cache writes with their 5-minute and 1-hour split, and price mode bills each at its own catalog rate. The Responses surface does not report Anthropic cache writes, so those tokens can only be billed as input there. Either client accepts any model in your catalog, so this is a recommendation rather than a constraint. In both cases api_key is your Router key, read here from RAMP_ROUTER_API_KEY, and model ids come from GET /v1/models. Router’s ids are account-specific.

OpenAI client

Anthropic client

Detection matches the host of the base URL: api.router.com, or any .router.com subdomain for a regional or staging endpoint. Both wrappers run the same check. The base-URL rules that identify Databricks and Snowflake still run first, so those keep their own provider. Everything on the OpenAI page still applies to the OpenAI client, and everything on the Anthropic page to the Anthropic one: streaming, async, per-call overrides. Streaming bills once, from the terminal usage event.

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.

Price mode

In pricing_mode="price", a Router call is priced from Router’s own catalog: the per-model rates in GET /v1/models for your account, which are the rates Router bills. Each entry carries rates for input, output, cache reads and cache writes, with separate 5-minute and 1-hour write rates where the vendor distinguishes them. Cost events carry price_source: "ramp_router".

No extra configuration

The catalog is account-scoped and requires your Router key. wrap() reads the key from the client you pass in and loads the catalog in the background before the first call, so the setup above is complete. The key is sent only to api.router.com.
Set ramp_router_api_key only to price Router usage without calling wrap(), for example when you build a CanonicalUsage yourself and pass it to emit(). An explicit value always wins over a learned one.

The served model is what is looked up

Router answers with the vendor’s own name for the model: a dated snapshot such as gpt-5.4-nano-2026-03-17 or claude-haiku-4-5-20251001, or a Fireworks path such as accounts/fireworks/models/.... The SDK resolves each of those back to its catalog entry by exact name, alias, or version suffix. A name that two entries claim at different rates is left unpriced rather than guessed.

Only the default tier is priced

Router reports the service tier each call was served at. The default tier bills at the catalog rate. flex and priority bill at rates the catalog does not publish, so the SDK does not price them. A call served at either tier emits token events and reports a PricingUnavailableError through on_error that names the tier:
To keep every call priced, pin service_tier to default in the request. Anthropic models do not accept a pinned tier; leave the field out and they serve the default. A response that reports no tier bills at the catalog rate.

Requests served with your own provider key

When Router serves a request with a provider key you brought, Router does not charge for it and the vendor bills you directly. The response is identical either way, so the SDK bills the catalog rate in both cases. The catalog rate matches what the vendor charges for the same model, so the amount is right and only the payee differs. Router falls back to its shared key when yours fails, and bills normally then.

Models Router bills off its own catalog

At the time of writing, Router bills a small set of OpenAI models at a fixed multiple of the rate its catalog publishes. The SDK bills the published rate and applies no correction, because the catalog is Router’s to maintain and a factor built into the SDK would be wrong for every customer the day the catalog changes. To match Router’s dashboard on these models, apply the multiple as a markup and remove it once the catalog is updated.
If your Router traffic is all one model, markup on LagoConfig does the same thing globally. See Markup.

Calls that fall back to token counts

Beyond the tier rule above, a Router call falls back to token events with a PricingUnavailableError when:
  • The catalog has not loaded yet on the very first call, or the model is not in your catalog.
  • The entry was served through a different backend than the one whose rate the catalog publishes. Some Fireworks entries also carry a Baseten alias (deepseek-ai/..., zai-org/..., moonshotai/...). A call served through Baseten bills Baseten’s rate, which the catalog does not publish, and the response names the model by that alias. The SDK leaves those unpriced rather than billing the wrong rate.
  • The model is gpt-3.5-turbo or gpt-4. Router serves them as gpt-3.5-turbo-0125 and gpt-4-0613, a snapshot suffix the SDK does not resolve.
The token events carry the exact counts, so nothing is lost. Set a rate for these cases in your Lago plan, as you would in token mode. Token mode itself, the default, is unaffected and emits the counts Router reported.

What gets captured

Events are stamped provider: "ramp_router" on both surfaces, and api says which surface answered. The two are stamped differently because they report tokens under different conventions. On /v1/responses, 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. On /v1/messages, fields are read from the Messages usage object exactly as on the Anthropic page: input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens and its ephemeral_5m_input_tokens / ephemeral_1h_input_tokens split, all additive. Non-Anthropic models served on this surface arrive in the same shape, with thinking tokens inside output_tokens.
In token mode, the cache fields are a subset of input on one surface and additive on the other. On /v1/responses, llm_input_tokens already contains the cached and written tokens, so bill llm_cached_input_tokens and llm_cache_creation_tokens there only as a discount, never as an addition. On /v1/messages they are additive, exactly as on a direct Anthropic call. Every token event carries its surface in the api property, so a filter on api keeps the two rules apart. See Subsets and additive fields. Price mode applies the right rule itself.
Three keys can 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

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. 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, and those cache-read and cache-write tokens are billed like any other. A proxy in front of Router is not detected. Reaching Router through your own hostname bills as plain OpenAI or plain Anthropic, with the wrong provider, the wrong price table 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

How price mode works, what a cost event carries, and the token-mode plan setup that keeps subset and additive fields apart.

Configuration reference

Every config knob, in both SDKs.