> ## 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.

# Usage metering

> Meter LLM usage straight from the LLM providers and gateways you already call, and turn tokens and cost into metered usage in Stigg.

Wrap the client you already use to call an LLM provider or an LLM gateway, and every completed call is metered: the usage the vendor reported is read from the response and sent to Stigg, attributed to the customer (and optionally the agent) that made it. There is no usage to count in your own code, and nothing changes in how you call the model.

## Supported providers and gateways

`stigg.wrap(client)` recognizes each of these from the client object itself, so you do not name the vendor. Each vendor's page shows how to wrap it and exactly what is reported.

### LLM providers

> **[Anthropic](/documentation/ai-agents/providers/anthropic)**
>
> Messages API, streaming included, with the full prompt-caching breakdown

> **[OpenAI](/documentation/ai-agents/providers/openai)**
>
> Chat Completions and Responses API, streaming included

> **[Google Gemini](/documentation/ai-agents/providers/gemini)**
>
> One-shot calls and chats, streaming included, with thinking tokens reported separately

> **[Mistral](/documentation/ai-agents/providers/mistral)**
>
> Chat, fill-in-the-middle and agent calls, streaming included

> **[Groq](/documentation/ai-agents/providers/groq)**
>
> Groq's own client or the OpenAI client, reported under Groq's own event name

> **[xAI](/documentation/ai-agents/providers/xai)**
>
> Grok models, through the OpenAI client

> **[DeepSeek](/documentation/ai-agents/providers/deepseek)**
>
> DeepSeek's own models, through the OpenAI client

> **[Perplexity](/documentation/ai-agents/providers/perplexity)**
>
> Models with built-in web search, through the OpenAI client

> **[Together AI](/documentation/ai-agents/providers/together)**
>
> A large catalog of open-source models, through the OpenAI client

> **[Fireworks AI](/documentation/ai-agents/providers/fireworks)**
>
> Open-source and fine-tuned models, through the OpenAI client

> **[Ollama](/documentation/ai-agents/providers/ollama)**
>
> Models you host yourself: meter and limit your own capacity

> **[vLLM](/documentation/ai-agents/providers/vllm)**
>
> Your self-hosted model server, through the OpenAI client

> **[TypeSafe](/documentation/ai-agents/providers/typesafe)**
>
> Jev, a model that answers typed questions about your data (yes or no, choice or score) in one call

### LLM gateways

> **[OpenRouter](/documentation/ai-agents/providers/openrouter)**
>
> Reports the credits actually charged and the model that actually served, through the OpenRouter or OpenAI client

> **[LiteLLM Proxy](/documentation/ai-agents/providers/litellm)**
>
> Self-hosted or cloud gateway: reports the cost LiteLLM computed for each call

> **[Amazon Bedrock](/documentation/ai-agents/providers/bedrock)**
>
> The Converse API, for every model Bedrock serves

> **[Cloudflare](/documentation/ai-agents/providers/cloudflare)**
>
> AI Gateway and Workers AI, with cache hits left unbilled and the model that served

> **[Databricks](/documentation/ai-agents/providers/databricks)**
>
> Model serving endpoints, with the model that actually answered each call

### Bring your own vendor

> **[Any OpenAI-compatible API](/documentation/ai-agents/providers/custom#any-openai-compatible-api)**
>
> Point the OpenAI client at any endpoint in OpenAI's format and it is metered with no extra code

> **[Custom adapter](/documentation/ai-agents/providers/custom)**
>
> Any other model or API, including your own: describe where its usage is and the SDK does the rest

## How usage is reported

* **Same names as the vendor.** Each metric keeps the name the vendor uses, such as `input_tokens` for Anthropic, `prompt_tokens` for OpenAI or `promptTokenCount` for Gemini. You can compare the numbers in Stigg directly with the vendor's own usage reports, such as its usage dashboard.
* **A total, and its parts.** Every vendor also reports a total: the vendor's own where it publishes one, such as `total_tokens` or `totalTokenCount`, and otherwise the sum of the parts, added up by the SDK (Anthropic, Ollama and TypeSafe). The total includes the other counters, so price the total or the parts, never both.
* **What a gateway charged, where it says.** OpenRouter and LiteLLM Proxy also report `cost`, what the gateway actually charged for the call, and the `model` that served it. See [Charging by tokens or by cost](#charging-by-tokens-or-by-cost).
* **Every usage field, not just the named metrics.** Each event also includes every usage field the vendor returned, with a `raw.` prefix (`raw.<field name>`). If a vendor adds a field the SDK does not report as a metric yet, you can build a meter on it without waiting for an SDK update.
* **No prompts or responses.** Only usage numbers are sent to Stigg. The text of your prompts and the model's responses never leaves your application.

Every completed call produces one **measurement**: the usage the vendor reported, under the vendor's own field names. The SDK turns that measurement into metrics with a [usage mapper](#usage-mappers), and reports them to Stigg through one of two [ingest modes](#ingest-modes).

## Ingest modes

The SDK reports usage in one of Stigg's two standard ways, chosen by the ingest mode:

| Mode | Reports usage as | Use it for |
| - | - | - |
| `async` (default) | [Raw events](/documentation/getting-usage-data-into-stigg/reporting-raw-events/index): one event per call, with every metric as a field | High-volume traffic and token pass-through pricing |
| `sync` | [Calculated usage](/documentation/getting-usage-data-into-stigg/reporting-calculated-usage): one report per metric | Balances that must reflect each call before the next check, such as credits that burn down in real time |

For how the two differ, including how quickly limits are enforced, see [Supported usage sources](/documentation/getting-usage-data-into-stigg/overview#supported-usage-sources).

You can set the mode for the whole SDK, for one wrapped client, or for a single call. A more specific setting takes precedence:

```typescript Whole SDK theme={null}
const stigg = new StiggAgentSDK(process.env.STIGG_SERVER_API_KEY, { ingest: 'sync' });
```

```typescript One client theme={null}
const anthropic = stigg.wrap(new Anthropic(), {
  stiggContext: { ingestMode: 'sync' },
});
```

```typescript One call theme={null}
await anthropic.messages.create({
  ...params,
  stiggContext: { ingestMode: 'sync' },
});
```

What is specific to the SDK:

* **Usage reported to a feature in `sync` mode must be a whole number.** Stigg's usage API accepts only whole numbers, so a fractional value is not reported and the failure is passed to `onError`. A credit type accepts fractional amounts in either mode, and so does an event in `async` mode.
* **Credits you calculate in code are deducted directly.** Usually the SDK reports raw metrics, such as token counts, and Stigg converts them into credits using your credit consumption settings. If you convert usage into credits yourself with a [usage mapper](#weighted-credits), the SDK deducts the result straight from the credit balance, in either mode, so it is not converted twice. It recognizes these credits by name: the mapper's output must use the ID of the credit type the call is checked against. For example, a mapper that outputs `{ credits: 3 }` for a call checked against `{ currencyId: 'credits' }` deducts 3 credits. The ID must match exactly: a mapper that outputs `{ data_credit: 3 }` for a call checked against `{ currencyId: 'credits' }` reports a feature named `data_credit`, which needs a meter like any other feature.
* **In `async` mode, deducted credits can take a moment to show.** The deduction is applied to the balance asynchronously, so a check made right after a call may not include it yet. Use `sync` when each call must be deducted before the next check.

## Configuring meters for async events

In `async` mode each call becomes one event:

* **The event name** is the vendor call that produced it, for example `anthropic.messages.create`. Each vendor's event names are listed on [its page](#supported-providers-and-gateways). Usage from a [gateway you operate](#a-gateway-you-operate) is named `<vendor>.proxied`, or the `event` you pass to `ingestFromHeaders()`.
* **Every metric** is a field on the event, under the names on the provider's page.

Override the name with `stiggContext.eventName`, for example `eventName: { name: 'llm.call' }`, to put several vendors behind one meter.

To meter a metric, create a [metered feature](/documentation/modeling-your-pricing-in-stigg/features/creating-features/metered-features) with a raw-events meter that filters on the event name and sums the field. For example, output tokens from Anthropic:

```json theme={null}
{
  "aggregation": { "function": "SUM", "field": "output_tokens" },
  "filters": [
    { "conditions": [{ "field": "eventName", "operation": "EQUALS", "value": "anthropic.messages.create" }] }
  ]
}
```

One event can feed any number of meters, so you can add an input-token feature, a cached-token feature, or a per-model breakdown later without changing code.

For example, an OpenRouter call made by an agent produces an event like this:

```json theme={null}
{
  "customerId": "customer-acme",
  "eventName": "openrouter.chat.completions.create",
  "idempotencyKey": "gen-1760262400-a1b2c3",
  "dimensions": {
    "prompt_tokens": 1200,
    "completion_tokens": 350,
    "cached_tokens": 800,
    "total_tokens": 1550,
    "cost": 4210,
    "model": "anthropic/claude-sonnet-5",
    "agentId": "agent-support-bot",
    "raw.prompt_tokens": 1200,
    "raw.completion_tokens": 350,
    "raw.total_tokens": 1550,
    "raw.cost": 0.00421
  }
}
```

The event follows Stigg's standard [raw event format](/documentation/getting-usage-data-into-stigg/reporting-raw-events/index), sent with the [Report events API](/api-and-sdks/api-reference/rest/events-report). Its `dimensions` hold:

* **The metrics**, under the names on the provider's page. Here `cost` is in millionths of an OpenRouter credit.
* **Your own `dimensions`**, and the agent ID under your agent type's attribution key (here `agentId`) when you set one. Use them as meter filters or for [governance attribution](/documentation/governance/core-concepts#entity-types).
* **For gateways, the `model`** that served the call.
* **Every usage field the vendor returned**, with a `raw.` prefix. Nested fields are flattened into single values.

You can see your own events under **Usage events** in the Stigg app.

> [!WARNING]
>
> An event whose name matches no meter is accepted and meters nothing. The events API returns only an acknowledgement, so nothing reports the mismatch. Configure the meter first, and check the first events under **Usage events** in the Stigg app. See [Viewing usage events](/documentation/getting-usage-data-into-stigg/viewing-usage-events).

## Usage mappers

By default the SDK reports exactly the usage the vendor returned, under the vendor's own metric names, so the numbers in Stigg match the vendor's own usage reports. This needs no configuration. Each vendor's metrics are listed on [its page](#supported-providers-and-gateways).

To report something else, set `stiggContext.usageMapper` to one of the mappers below, or to your own. Set it on `wrap()` as the client's default, or pass it with each call to override the default for that call.

### Weighted credits

Converts each call's usage into credits in your code, using a rate table, and deducts them straight from the credit balance.

Usually you do not need this. The standard way to charge credits for tokens is in Stigg: create a metered feature for each token metric and set its credit cost in the credit type's **Credit consumption** settings. See [Credits](/documentation/modeling-your-pricing-in-stigg/credits/overview). Use weighted credits instead when:

* **You want the rates in code**, for example to charge different rates per model by passing a different mapper in each call's `stiggContext`, instead of creating a feature for each case in Stigg.
* **You want to skip meters.** Credits calculated in code are deducted straight from the balance, with no metered feature or meter to configure. With `sync` mode, the next check already includes them.

`weightedMetrics()` turns a rate table into a mapper that reports one number, typically to a credit type or a single metered feature:

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

const anthropic = stigg.wrap(new Anthropic(), {
  stiggContext: {
    entitlement: { currencyId: 'credits' },
    // 1 credit per 1,000 input tokens, 5 per 1,000 output tokens. Cache reads are free.
    usageMapper: weightedMetrics(
      anthropicRecipe,
      'credits',
      { input_tokens: 1, output_tokens: 5 },
      { per: 1_000 },
    ),
  },
});
```

* The table is typed against the recipe's own metric names, so a typo does not compile.
* A metric left out of the table is free.
* The total is **not rounded**. A credit type accepts fractional amounts, so a call worth a fifth of a credit costs a fifth of a credit. When the target is a metered feature in `sync` mode, which accepts only whole numbers, choose rates and a `per` that divide evenly, or round in [your own mapper](#your-own-mapper).
* A call that measured nothing reports nothing.

For a single rate across all token types, `unitsPerTokens('credits', 1_000)` reports one unit per 1,000 tokens of any kind. It does not round either.

> [!NOTE]
>
> A call attributed to an agent is the exception: Governance counts agent and team usage in whole units, so the SDK rounds the amount up to the next whole unit and logs that it did. See [Usage governance](/documentation/ai-agents/usage-governance#2-make-calls-as-the-agent).

A mapper can also be passed with each call instead of on `wrap()`, like any other `stiggContext` field. This keeps the wrapped client generic, for example to charge a different rate per model:

```typescript theme={null}
const anthropic = stigg.wrap(new Anthropic());

const rates = {
  'claude-opus-5': weightedMetrics(anthropicRecipe, 'credits', { input_tokens: 15, output_tokens: 75 }, { per: 1_000 }),
  'claude-haiku-5': weightedMetrics(anthropicRecipe, 'credits', { input_tokens: 1, output_tokens: 5 }, { per: 1_000 }),
};

const model = 'claude-haiku-5';
const message = await anthropic.messages.create({
  model,
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Summarize this ticket' }],
  stiggContext: {
    customerId: 'customer-acme',
    entitlement: { currencyId: 'credits' },
    usageMapper: rates[model],
  },
});
```

### Normalized metrics across vendors

Vendors disagree on whether cached tokens are already counted inside the input total: OpenAI and Gemini include them, while Anthropic and Amazon Bedrock count cache reads and writes separately. Each vendor's page says which applies under **What is reported**.

To price every vendor with one set of features, use `normalizedMetrics`. It reports four non-overlapping counters under SDK-owned names: `stigg_input_tokens`, `stigg_output_tokens`, `stigg_cache_read_tokens` and `stigg_cache_creation_tokens`.

```typescript theme={null}
import { normalizedMetrics } from '@stigg/agent-sdk-js';

stigg.wrap(client, { stiggContext: { usageMapper: normalizedMetrics } });
```

### Your own mapper

When none of the built-in mappers fits, write your own. A mapper is a plain function: it receives the usage the recipe measured for a call, and returns what to report, as a `{ featureOrCurrencyId: value }` record. Each recipe exports a type for what it receives:

```typescript theme={null}
import type { AnthropicMetrics, Metrics } from '@stigg/agent-sdk-js';

const dataCredits = ({ metrics = {} }: { metrics?: AnthropicMetrics }): Metrics => {
  const weighted = (metrics.output_tokens ?? 0) * 5 + (metrics.input_tokens ?? 0);
  return weighted > 0 ? { data_credit: Math.ceil(weighted / 100) } : {};
};
```

Pass it as the `usageMapper`:

```typescript theme={null}
const anthropic = stigg.wrap(new Anthropic(), {
  stiggContext: { usageMapper: dataCredits },
});
```

Every counter is optional, because a vendor omits a counter (such as cache reads) when it is zero. A value reported to a feature in `sync` mode must be a whole number.

The mapper also receives the rest of the measurement: `usage` (the four normalized token counters), `dimensions`, and `estimated`. `estimated` is `true` when a count is the SDK's estimate rather than the vendor's. This happens when a streamed Anthropic call is stopped before it finishes: Anthropic reports the output count only at the end of the stream, so the SDK estimates the output from the text received so far, at about four characters per token. To meter only counts the vendor reported, return `{}` when `estimated` is set:

```typescript theme={null}
import type { Measurement, Metrics } from '@stigg/agent-sdk-js';

const vendorCountsOnly = (measurement: Measurement): Metrics =>
  measurement.estimated ? {} : (measurement.metrics ?? {});
```

The Agent SDK is [open source](https://github.com/stiggio/stigg-agent-sdk-ts) under the MIT license. If your mapper would be useful to others, for example a pricing scheme common in your industry, contribute it to the [Agent SDK on GitHub](https://github.com/stiggio/stigg-agent-sdk-ts/blob/main/CONTRIBUTING.md) so other developers can use it too.

## Metering through a gateway

An LLM gateway sits between your code and the LLM providers. It lets you:

* **Work with one API across many LLM providers**, often with failover, caching and routing between models.
* **Set up one commercial agreement**, with the gateway, while still using models from many providers.

When you call a gateway, you wrap the client you use to call it, the same way as for an LLM provider. Each call is metered like any other, and some gateways also report what only the gateway knows: what the call really cost, and which model really served it. What each gateway reports is on [its page](#llm-gateways).

### Charging by tokens or by cost

An [OpenRouter](/documentation/ai-agents/providers/openrouter) or [LiteLLM Proxy](/documentation/ai-agents/providers/litellm) call reports two kinds of numbers: the token counts, and `cost`, what the gateway charged for the call. You choose which one usage is measured in:

* **Tokens**, to treat calls the same way whether they go straight to a provider or through a gateway. Meter the token metrics.
* **`cost`**, to follow what the gateway actually charged, for example to pass it on with your margin. Meter the `cost` metric.

Either one can drive any pricing model in Stigg. Customers can be invoiced for their usage, or they can pay upfront for credits, through a [top-up](/documentation/modeling-your-pricing-in-stigg/credits/self-service-top-up) or a plan that includes credits, and each call burns credits from their balance. Set it up in Stigg as usual: create a metered feature for the metric, add it to your plans, and for credits, map the feature to a credit cost in the credit type's **Credit consumption** settings. See [Configuring meters for async events](#configuring-meters-for-async-events) and [Credits](/documentation/modeling-your-pricing-in-stigg/credits/overview).

> **Optional: convert cost into credits in your code**
>
> 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](#weighted-credits).
>
> ```typescript theme={null}
> import { openrouterRecipe, weightedMetrics } from '@stigg/agent-sdk-js';
>
> const recipe = openrouterRecipe();
>
> const router = stigg.wrap(new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }), {
>   recipe,
>   stiggContext: {
>     entitlement: { currencyId: 'credits' },
>     // 1 Stigg credit per 1,000 millionths of an OpenRouter credit.
>     // Each call still passes its own customerId.
>     usageMapper: weightedMetrics(recipe, 'credits', { cost: 1 }, { per: 1_000 }),
>   },
> });
> ```

### 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

```mermaid theme={null}
flowchart LR
    App["Your application"] -->|"request + x-stigg-* headers"| Px["Your proxy"] --> P["Model provider"]
    App -.->|"check before the call"| S[("Stigg")]
    Px -.->|"report usage after the call"| S
```

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

```typescript theme={null}
const headers = stigg.governanceHeaders({
  customerId: 'customer-acme',
  agentId: 'drafter',
  attributionKey: 'agentId',
});
// { 'x-stigg-customer-id': 'customer-acme',
//   'x-stigg-dimensions': '{"agentId":"drafter"}',
//   'x-stigg-attribution-key': 'agentId',
//   'x-stigg-run-id': '...' }

await fetch(`${PROXY_URL}/v1/chat/completions`, {
  method: 'POST',
  headers: { 'content-type': 'application/json', ...headers },
  body: JSON.stringify(params),
});
```

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

#### In your proxy

```typescript theme={null}
import { StiggAgentSDK } from '@stigg/agent-sdk-js';

const stigg = new StiggAgentSDK(process.env.STIGG_SERVER_API_KEY);

// Inside your request handler, after the upstream call returns:
const completion = await upstream.chat.completions.create(params);

await stigg.ingestFromHeaders(req.headers, {
  vendor: 'openai',                 // the response shape the proxy observed
  usage: completion.usage,
  idempotencyKey: completion.id,    // the upstream request ID
  entitlement: { currencyId: 'credits' },
});
```

| Header | Content |
| - | - |
| `x-stigg-customer-id` | The customer the request belongs to. Requests without it are not attributed. |
| `x-stigg-dimensions` | A JSON object of attribution dimensions, such as the agent ID. |
| `x-stigg-attribution-key` | Which key in `x-stigg-dimensions` holds the agent ID, when the context includes an `agentId`. |
| `x-stigg-run-id` | A correlation ID for work that spans processes. |

`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.

> [!WARNING]
>
> 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.

## Idempotency

Every report carries an idempotency key, so a retried report is not counted twice:

* When the vendor returns a request ID that is stable across its own retries (for example an OpenRouter generation ID), that ID is used.
* Otherwise the SDK generates one per call.
* Set `stiggContext.idempotencyKey` on a call to choose your own, for example when you resume a stream and report the same turn again.

In `sync` mode each metric is keyed separately as `<key>:<metric>`.

## Errors and flushing

Reporting never breaks the call. If a report fails, the response is still returned and the failure is passed to `onError`:

```typescript theme={null}
const stigg = new StiggAgentSDK(process.env.STIGG_SERVER_API_KEY, {
  onError: (error, context) => {
    // context: { operation, customerId, idempotencyKey, runId }
    myTelemetry.capture(error, context);
  },
});
```

By default `onError` logs a warning, so failures are visible before you wire it up.

Reports are sent in the background. In serverless functions, request-scoped workers and CLIs, wait for them to be sent before the process exits:

```typescript theme={null}
await anthropic.stigg.flush();          // one wrapped client
await stigg.flush({ close: true });     // every client, then release connections
```

`flush()` returns a summary of what was reported: the number of checks and ingests, failures, and the totals per metric.

## Troubleshooting

### The credit balance does not go down

A credit balance only goes down when the feature you report is mapped to that credit type in Stigg. If it is not, every call succeeds and every report is accepted, but the balance never changes. In `sync` mode the SDK detects this from Stigg's response and logs:

```text theme={null}
[stigg] reported input_tokens but the response carried no credit balance - this feature is not
mapped to credits, so the budget this run checks against will never drain.
```

Fix it by [mapping the feature to the currency](/documentation/modeling-your-pricing-in-stigg/credits/credit-consumption-logic), or by reporting to the currency directly with a [weighted mapper](#weighted-credits).


## Related topics

- [BYOC architecture](/documentation/high-availability-and-scale/byoc/architecture.md)
- [Metering architecture](/documentation/high-availability-and-scale/byoc/metering-architecture.md)
- [Overview](/documentation/getting-usage-data-into-stigg/overview.md)
- [Choosing an ingestion method](/documentation/getting-usage-data-into-stigg/choosing-an-ingestion-method.md)
- [Ingestion from GCP Pub/Sub](/documentation/getting-usage-data-into-stigg/reporting-raw-events/ingestion-from-gcp.md)


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