> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stigg.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Bill LLM gateway spend

> Charge customers the real cost of their LLM calls through OpenRouter or LiteLLM, plus your margin, from a credit balance.

In this guide you bill customers for what their calls actually cost, plus a margin, by drawing down a prepaid credit balance. The cost comes from the gateway itself, so it reflects caching, routing and fallbacks rather than a price list you maintain by hand.

**What you will build:**

* A credit balance that drains by the gateway's real cost times your markup, call by call.
* A pre-call check that denies access when the balance is empty and offers to buy more credits with a [top-up](/documentation/modeling-your-pricing-in-stigg/credits/self-service-top-up).
* A `model` dimension on every report, so you can see when routing changes your costs.

**Before you start:**

* A Stigg server API key.
* An [OpenRouter](https://openrouter.ai) account and key, or a [LiteLLM Proxy](https://docs.litellm.ai/docs/simple_proxy) you run.
* A credit currency in Stigg (this guide uses `credits`, where 1 credit is worth 1 US cent) and a customer with a credit grant. See [Set up credits](/guides/i-want-to/set-up-credits).

<Steps>
  <Step title="Decide your conversion">
    OpenRouter reports cost in micro-credits by default (1 OpenRouter credit is 1 US dollar, so 1,000,000 micro-credits is $1). LiteLLM reports micro-dollars by default. Either way, one unit is $0.000001.

    With 1 Stigg credit = \$0.01 and a 20% markup:

    ```text theme={null}
    credits = cost_in_micro_units × 1.2 / 10,000
    ```

    A call that cost the gateway \$0.00108 (1,080 micro-units) draws 0.1296 credits. Credit currencies accept fractional amounts, so the SDK does not round. The exception is a call attributed to an agent, which is rounded up to a whole credit because Governance counts agent usage in whole units. If you attribute calls to agents, choose a credit unit small enough that this rounding does not matter for your prices.
  </Step>

  <Step title="Install and initialize">
    ```bash theme={null}
    npm install @stigg/agent-sdk-js openai
    ```

    ```typescript theme={null}
    import OpenAI from 'openai';
    import { StiggAgentSDK, openrouterRecipe, weightedMetrics } from '@stigg/agent-sdk-js';

    const stigg = new StiggAgentSDK(process.env.STIGG_SERVER_API_KEY);
    ```
  </Step>

  <Step title="Wrap the gateway client">
    <Tabs>
      <Tab title="OpenRouter">
        This example uses the `OpenAI` client, which accepts a different customer on each call. You can also wrap OpenRouter's own client, `OpenRouter` from `@openrouter/sdk`, with the same recipe options. Its calls do not accept a `stiggContext`, so set the customer in `wrap()`. See [OpenRouter](/documentation/ai-agents/llm-wrappers/providers/openrouter).

        ```typescript theme={null}
        const recipe = openrouterRecipe();

        export const llm = stigg.wrap(
          new OpenAI({ baseURL: 'https://openrouter.ai/api/v1', apiKey: process.env.OPENROUTER_API_KEY }),
          {
            recipe,
            stiggContext: {
              entitlement: { currencyId: 'credits' },
              usageMapper: weightedMetrics(recipe, 'credits', { cost: 1.2 }, { per: 10_000 }),
              ingestMode: 'sync',
            },
          },
        );
        ```
      </Tab>

      <Tab title="LiteLLM Proxy">
        ```typescript theme={null}
        import { litellmRecipe } from '@stigg/agent-sdk-js';

        const recipe = litellmRecipe({ baseUrl: process.env.LITELLM_URL });

        export const llm = stigg.wrap(
          new OpenAI({ baseURL: process.env.LITELLM_URL, apiKey: process.env.LITELLM_KEY }),
          {
            recipe,
            stiggContext: {
              entitlement: { currencyId: 'credits' },
              usageMapper: weightedMetrics(recipe, 'credits', { cost: 1.2 }, { per: 10_000 }),
              ingestMode: 'sync',
            },
          },
        );
        ```

        <Warning>
          LiteLLM returns its cost in a response header, which a streamed call cannot carry. Make billed calls without `stream: true`, or add token weights to the table so streamed calls are still billed.
        </Warning>
      </Tab>
    </Tabs>

    * `entitlement` turns on the pre-call balance check.
    * `usageMapper` converts the gateway's `cost` into credits. Because the metric is named after the currency being checked, it draws the balance directly.
    * `ingestMode: 'sync'` updates the balance with each call, so the next check sees it right away.
  </Step>

  <Step title="Make calls and handle an empty balance">
    ```typescript theme={null}
    try {
      const completion = await llm.chat.completions.create({
        model: 'anthropic/claude-opus-5',
        messages,
        stiggContext: { customerId: account.stiggCustomerId },
      });
      return completion;
    } catch (err) {
      const decision = stigg.denial(err);
      if (!decision) throw err;
      // decision.remedy === 'top-up'
      return renderTopUpPrompt(decision.message);
    }
    ```

    Denied access is never cached, so a customer who buys more credits can continue on their very next call.
  </Step>

  <Step title="Verify">
    Make a few calls and open the customer's credit balance in the Stigg app. Each call draws its marked-up cost. Check `onIngest` or the wrapped client's `stigg.summary()` to compare what was reported with the gateway's own dashboard.

    ```typescript theme={null}
    console.log(await llm.stigg.flush());
    // { checks: 3, ingested: 3, reported: { credits: 0.3888 }, ingestFailures: 0, ... }
    ```
  </Step>

  <Step title="Watch for routing drift (optional)">
    Each report carries the model that actually served as a `model` dimension. When the gateway falls back from the model you requested, your cost changes and the dimension shows it. To analyze it over time, meter a second client or feature in `async` mode and add a [raw-events meter](/documentation/getting-usage-data-into-stigg/reporting-raw-events/index) that filters on `model`.
  </Step>
</Steps>

## Things to know

* **The check asks for one unit by default.** A customer with almost no credits left can make one more call, which can cost more than one unit. If that matters, give the recipe an `estimate` that returns an upper bound. See [Gating on cost](/documentation/ai-agents/llm-wrappers/gateways#gating-on-cost).
* **Responses with no cost** (bring-your-own-key on OpenRouter, some upstreams) report no `cost` and therefore draw no credits. Add token weights to the table if you need a fallback.
* **Do not meter the same traffic twice.** If you also run a proxy that reports with `ingestFromHeaders()`, exclude the traffic you already wrap.


## Related topics

- [Meter LLM token usage](/guides/ai-agents/meter-llm-token-usage.md)
- [LLM and gateway wrappers](/documentation/ai-agents/llm-wrappers/overview.md)
- [Gateways](/documentation/ai-agents/llm-wrappers/gateways.md)
- [Supported providers and gateways](/documentation/ai-agents/llm-wrappers/supported-providers.md)
- [Meter usage from your own gateway](/guides/ai-agents/meter-usage-from-your-own-gateway.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.