Skip to main content
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.

Ingest modes

The SDK reports usage in one of Stigg’s two standard ways, chosen by the ingest mode: For how the two differ, including how quickly limits are enforced, see 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:
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, 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. 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 with a raw-events meter that filters on the event name and sums the field. For example, output tokens from Anthropic:
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:
The event follows Stigg’s standard raw event format, sent with the Report events API. 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.
  • 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.
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.

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. 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. 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:
  • 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.
  • 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.
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.
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:

Normalized metrics across vendors

Vendors disagree on whether cached tokens are already counted inside the input total: 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.

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:
Pass it as the usageMapper:
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:
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:
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:
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:
Fix it by mapping the feature to the currency, or by reporting to the currency directly with a weighted mapper.