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

# Metering usage

> What the Stigg Agent SDK reports for each LLM call, how to configure meters for it, and how to turn tokens into credits.

Every completed call through a wrapped client 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**, and reports them to Stigg through one of two **ingest modes**.

```mermaid theme={null}
flowchart LR
    R["Provider response<br/>usage block"] --> M["Measurement<br/><i>vendor metrics, normalized tokens, raw usage</i>"]
    M --> UM["usageMapper<br/><i>default: report vendor metrics as-is</i>"]
    UM --> I{"ingestMode"}
    I -- "async (default)" --> E["One event per call<br/>events API"] --> MT["Raw-events meters<br/>in Stigg"]
    I -- sync --> U["One usage report per metric<br/>usage API"] --> F["Features and credits<br/>updated immediately"]
```

## 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 billing |
| `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 drain 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:

<CodeGroup>
  ```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' },
  });
  ```
</CodeGroup>

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 currency 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 currency 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 provider's event names are listed on its page, and all of them in [Supported providers](/documentation/ai-agents/llm-wrappers/supported-providers#at-a-glance). Usage from a proxy 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 bill 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).
</Warning>

## 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 provider's metrics are listed on its page, and all of them in [Supported providers](/documentation/ai-agents/llm-wrappers/supported-providers#at-a-glance).

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 currency'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 currency 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 currency 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 [Agent governance](/documentation/ai-agents/agent-governance#2-make-calls-as-the-agent).
</Note>

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:

| Vendor | Input field | Includes cached tokens? |
| - | - | - |
| Anthropic | `input_tokens` | No. Cache reads and writes are separate. |
| OpenAI Chat Completions | `prompt_tokens` | Yes, includes `cached_tokens` |
| OpenAI Responses | `input_tokens` | Yes, includes `cached_tokens` |
| Gemini | `promptTokenCount` | Yes, includes `cachedContentTokenCount` |
| Amazon Bedrock | `inputTokens` | No. Cache reads and writes are separate. |

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 billable = (metrics.output_tokens ?? 0) * 5 + (metrics.input_tokens ?? 0);
  return billable > 0 ? { data_credit: Math.ceil(billable / 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 bill 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. 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 so other developers can use it too.

## 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, drain them 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 currency 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)
- [Local caching and fallback strategy](/documentation/high-availability-and-scale/local-caching-and-fallback-strategy.md)
- [Core concepts](/documentation/modeling-your-pricing-in-stigg/credits/credits-core-concepts.md)
- [Persistent cache, fallback, and offline mode](/documentation/high-availability-and-scale/sidecar/resilience.md)
- [Fallback strategy](/documentation/managing-customers-and-subscriptions/entitlements/fallback-strategy.md)


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