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:
gen_ai.usage.costai.usage.costtd.cost_usdtelemetry.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:
- The model as received
- The model without a version or date suffix
- An Anthropic form that changes
claude-N-M-toclaude-N.M- - 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.