Skip to main content
An LLM gateway sits between your code and the model providers: one endpoint, many models, with failover, caching and its own cost log. Gateways know two things a plain token count cannot tell you:
  • What the call really cost. The gateway prices each call itself, including its own caching and markups.
  • Which model really served. When the model you asked for is down or rate-limited, the gateway falls back to another one. Your cost changes and nothing in your code notices.
There are two ways a gateway can sit in your stack, and each needs a different integration:

A gateway you call

The gateway’s response comes back into your process, so you wrap the client you use to call the gateway, the same way you would for a model provider. The gateway’s recipe reports the token counts, plus what only the gateway knows, when it returns it:

OpenRouter

The SDK recognizes both ways of calling OpenRouter and selects the OpenRouter recipe automatically: OpenRouter’s own client, OpenRouter from @openrouter/sdk, and an OpenAI client whose base URL is on openrouter.ai. Both report the same metrics under the same names, so one meter covers either client. See OpenRouter for each client’s event names.
OpenRouter returns usage on a streamed call only when the request asks for it: streamOptions: { includeUsage: true } with the OpenRouter client, or stream_options: { include_usage: true } with the OpenAI client. Without it, the stream reports no usage and the call is not metered. OpenRouter charges in credits, and a single call usually costs a small fraction of one credit. The recipe reports cost as a whole number, so by default it counts in millionths of a credit: a call that cost 0.0003 credits is reported as 300. If the feature or currency you bill in Stigg uses a different unit, pass the recipe yourself with unitsPerCredit. For example, to report in hundredths of a credit:
A larger unit rounds each call up to a whole unit, so cheap calls are overcharged. Keep the default unless your pricing requires otherwise.

openrouterRecipe options

LiteLLM Proxy

LiteLLM Proxy’s API follows OpenAI’s format, so you call it with the OpenAI client and its token counts use OpenAI’s field names. Whether you host the proxy yourself or use LiteLLM’s cloud-hosted version, each proxy has its own address, so the SDK cannot recognize it automatically. Pass the recipe with your proxy’s base URL:
LiteLLM returns its price in the x-litellm-response-cost response header. Headers are sent before generation starts, so streamed LiteLLM calls report tokens but no cost. Non-streaming calls report both.

litellmRecipe options

Amazon Bedrock

Bedrock serves models from many vendors through one API, the Converse API. Wrap a BedrockRuntimeClient from @aws-sdk/client-bedrock-runtime. The SDK recognizes it automatically. Bedrock differs from the other gateways in two ways:
  • No cost. Bedrock returns token counts but no price, so you can bill tokens but not the gateway’s cost.
  • No routing drift. Bedrock serves exactly the model you name, so there is no model dimension.
See Amazon Bedrock for setup and what is reported.

Billing tokens or billing cost

An OpenRouter or LiteLLM call reports two kinds of numbers: the token counts, and cost, what the gateway charged for the call. You choose which one your pricing is based on:
  • Bill tokens to price calls the same way whether they go straight to a provider or through a gateway. Meter the token metrics.
  • Bill cost to pass on what the gateway actually charged, plus your margin. Meter the cost metric.
Either way, you set up pricing in Stigg as usual: create a metered feature for the metric, add it to your plans, and if you sell credits, map the feature to a credit cost in the credit currency’s Credit consumption settings. See Configuring meters for async events and Credits.
Instead of mapping a feature to credits in Stigg, the SDK can convert usage into credits before it is reported, and deduct them straight from the credit balance. Use this only when the conversion has to live in code. See Weighted credits.

Gating on cost

By default a gateway check uses a requested usage of 1, which asks “is there anything left?”, and the real amount is reported after the call. When billing tokens, a customer can go over the limit by about one call. When billing cost in millionths of a credit, one call can be thousands of units, so a customer with one unit left can pass the check and then spend well past it. If that matters for your product, pass an estimate that returns an upper bound for the call, for example from max_tokens and the highest rate you expect to pay. It is used as the requested usage.

Routing drift

The model dimension records the model that served, not the one you asked for. Filter or group a meter by it in Stigg to see how often your traffic falls back, and how that changes your cost.

A gateway you operate

A proxy you run is a separate process. It sees requests, not the customer or agent behind them, and the customer’s budget lives in your application. So the work splits across the boundary:
  1. Your application checks the budget in-process, as usual, and sends attribution with the request as x-stigg-* headers.
  2. Your proxy forwards the request, reads usage off the provider’s response, and reports it with stigg.ingestFromHeaders().
Governance decisions never leave your application. The proxy only meters.

In your application

A wrapped client can build the same headers with its own stigg.headers(context).

In your proxy

vendor is one of anthropic, openai (Chat Completions), openai-responses, gemini or typesafe. It is named rather than guessed because several vendors share field names (input_tokens appears in both Anthropic and OpenAI Responses usage), and a wrong guess would report to the wrong meter. If you omit it, pass a metrics mapper so the SDK knows what to report.
Do not meter the same traffic in both places. If your application wraps its client and your proxy calls ingestFromHeaders() for the same requests, the usage is reported twice. Wrap in the application for calls that return into your process, and use the header path only for traffic the application cannot meter itself.