Skip to content
telemetry.dev
Esc
↑↓navigate↵open⌘Jpreview
On this page

Cost calculation

Learn how telemetry.dev calculates model cost from token usage and model prices.

telemetry.dev calculates span cost at ingest. The server uses normalized token usage and a vendored pricing snapshot compiled from models.dev and supplementary catalogs.

A client cost value has priority over the server calculation.

Client cost override

The first-party SDKs map costUsd in TypeScript and cost_usd in Python to gen_ai.usage.cost. If this value is present and valid, the ingest API stores it without a server calculation.

The ingest API checks client cost attributes in this order:

  1. gen_ai.usage.cost
  2. ai.usage.cost
  3. td.cost_usd
  4. telemetry.dev.cost_usd

A client cost must be a nonnegative number. The ingest API also accepts a decimal string with at most eight fractional digits.

A client cost does not require a model match. Thus, you can send gen_ai.usage.cost for a custom or private model.

Server calculation

The pricing snapshot gives prices in US dollars per 1,000,000 tokens. The server calculates these token kinds:

Token kind Normalized usage Price field
Noncached input Input minus cache read and cache creation input
Cache read Cache-read input tokens cachedInput, or input as a fallback
Cache creation Cache-creation input tokens cacheWrite, or input as a fallback
Output Output tokens output
Reasoning Reasoning output tokens No separate price

The calculation is:

nonCached = max(0, input - cacheRead - cacheCreation)

cost = (
  nonCached    * price.input
  + cacheRead  * (price.cachedInput ?? price.input)
  + cacheCreation * (price.cacheWrite ?? price.input)
  + output     * price.output
) / 1_000_000

The server accepts reasoning-token attributes, but it does not add a separate reasoning charge. Providers count those tokens inside output usage.

If output usage is absent, normalization can use reasoning output as the output total. If input usage is absent, normalization can use cache-read plus cache-creation usage.

The server formats the result with at most eight decimal places. The stored cost has eight decimal places of precision.

Modality pricing

Some providers price image and audio tokens differently from text tokens. When a span carries the modality usage attributes, the server splits each aggregate count into a text remainder and the reported image and audio parts, and prices each part with the matching rate from the snapshot:

Part Price field Fallback
Image input, noncached imageInput input
Audio input, noncached audioInput input
Image cache read imageCachedInput cachedInput, then input
Audio cache read audioCachedInput cachedInput, then input
Image output imageOutput output
Audio output audioOutput output
Text and any remainder input, cachedInput, output —

A modality’s noncached input is its input count minus its cache-read count. If the span reports a positive aggregate cache-read count but no cache-read count for a modality, that modality’s noncached input stays at the base input rate. Any cache creation keeps all noncached modality input at the base rate, because the server cannot separate cache overlap per modality without charging twice. Reported image and audio cache reads still use their cache-read rates, and cache creation still uses cacheWrite.

When a span has no aggregate input, cache-read, or output count, the server uses the sum of the modality counts for that kind.

Cost is null when the modality detail contradicts the aggregate counts:

  • Image plus audio input allocated to modality-specific noncached pricing exceeds the noncached input total. Modalities that fall back to base pricing under the rules above are excluded from this check.
  • Modality cache reads above the cache-read total, or above their own modality’s input
  • Image plus audio output above the output total
  • Cache reads plus cache creation above the input total, when the server derived the input or cache-read total from modality counts

Send consistent aggregate and modality counts, or send a client cost.

Snapshot entries without image or audio rates price every modality at the text rate. These are token-cost estimates. They do not include per-image generation fees, service-tier modifiers, or other charges that a provider bills outside of token counts.

Usage attributes

The primary first-party usage attributes are:

Usage Attribute
Input gen_ai.usage.input_tokens
Output gen_ai.usage.output_tokens
Total fallback gen_ai.usage.total_tokens
Cache read gen_ai.usage.cache_read.input_tokens
Cache creation gen_ai.usage.cache_creation.input_tokens
Reasoning output gen_ai.usage.reasoning.output_tokens
Modality detail gen_ai.usage.{text,image,audio}.{input_tokens,output_tokens,cache_read.input_tokens}

The ingest API also normalizes compatibility attributes. See Span attributes for the complete priority order.

Pricing snapshot

The ingest service vendors a pricing snapshot into its deployment image. The build process compiles it from four catalogs and refreshes it for a build, test, development run, or deployment:

Source Role
models.dev Primary prices for direct and hosted providers
LiteLLM Fills provider and model entries that models.dev lacks, and image or audio rates that a models.dev entry lacks
Vercel AI Gateway Prices for Vercel gateway routes only
OpenRouter Prices for OpenRouter routes only

Gateway catalogs price only their own routes. A gateway route that is absent from its catalog is not priced from another provider’s entry for the same model.

If the refresh is not successful, an existing snapshot stays in use. The ingest API does not request any catalog for each span.

For models with context-tier pricing, the total reported input count, including cache reads and cache creation, selects the rates for the whole request. The server does not charge only the tokens above the threshold at the higher rate. A tiered model with no input count has null cost, because the server cannot select a tier.

The snapshot file is generated and is not in the source repository. As a result, the repository does not prove a fixed model count.

Model matching

Before a lookup, the server trims and lowercases the model. It also removes these Google Vertex model prefixes:

  • projects/…/publishers/google/models/
  • publishers/google/models/
  • models/

The server lowercases the provider and applies these provider mappings:

Received provider Lookup provider
gcp.vertex_ai, vertex, vertex_ai google-vertex
gcp google
aws, bedrock, bedrock_converse amazon-bedrock
azure.ai.inference azure_ai
gateway, vercel_ai_gateway, vercel ai gateway vercel
fireworks_ai fireworks-ai
ibm.watsonx.ai watsonx
mistral_ai mistral
together_ai togetherai
x_ai xai
Any other provider with . The segment before the first ., then the same mappings

The lookup builds these model forms:

  1. The model as received
  2. The model without a version or date suffix
  3. An Anthropic form that changes claude-N-M- to claude-N.M-
  4. That Anthropic form without a version or date suffix

It first tries provider:model for each form. If none matches, it tries each form without a provider. The vercel and openrouter providers skip the second pass, because gateway catalogs price only their own routes.

The suffix matcher removes a - or @ followed by YYYY-MM-DD, YYYYMMDD, vN, latest, or three digits.

Unknown models and absent usage

If no snapshot entry matches the model, the calculated cost is null. If each token field is null, the calculated cost is also null.

A real zero stays different from null. Trace cost totals stay null until at least one span supplies a cost.

Use gen_ai.usage.cost when the pricing snapshot cannot identify your model or price. Client cost remains the highest-priority value.

Last updated on August 3, 2026

Was this page helpful?