Research-backed calculator guide
OpenTelemetry to Invoice Cost Mapping: A Field-Level Crosswalk
Map OpenTelemetry GenAI fields to provider usage exports and invoice truth without confusing trace estimates with billed cost.
Quick answer
OpenTelemetry is an attribution layer, not an invoice. The core GenAI fields tell you which provider and model handled a call, how many input and output tokens were observed, and how the call fits inside a trace. They do not, by themselves, prove the amount charged by a provider. The reliable crosswalk is therefore two-stage: use OTel fields to explain *who or what caused usage*, then use the provider usage export and invoice to establish *what was billed*.
For a practical OpenTelemetry LLM cost mapping, preserve gen_ai.provider.name, gen_ai.request.model, gen_ai.response.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, cache-token fields, reasoning-token fields, gen_ai.response.id, and your own service, environment, tenant, and workflow identifiers. Map those fields to provider-native meters only after checking their definitions. Then reconcile by time bucket, model or SKU, account or project, and currency. Keep trace-derived dollars labeled as estimates until the provider export or invoice confirms them.
The distinction matters because providers can apply discounts, credits, batch or priority rates, regional routing, minimums, taxes, and non-token charges that are not represented in a normal span. OpenTelemetry’s semantic-convention pages also document a move of GenAI conventions to a separate repository, so treat the field version and migration state as part of your data contract rather than assuming every gen_ai.* field is equally stable. [1] [2]
What each OTel field can and cannot prove
The following crosswalk is a starting schema for a trace-to-invoice ledger. “Invoice-side match” means the field can help select or group provider records; it does not mean that the provider necessarily exposes the same field or request-level granularity.
| OTel field or family | What it tells your application | Invoice-side counterpart or join aid | What remains unproven |
|---|---|---|---|
gen_ai.provider.name | Provider identified by instrumentation | Provider account, contract, or billing source selected for the reconciliation run | That the provider label matches the legal billing entity or marketplace reseller |
gen_ai.request.model and gen_ai.response.model | Requested model and the model that returned the response | Provider model, SKU, deployment, or line-item description | Regional SKU, dated snapshot, service tier, or routed model unless exported separately |
gen_ai.usage.input_tokens | Input tokens recorded for the GenAI operation | Input-token meter in a provider usage report | Whether the number includes cached, cache-write, audio, image, or Scale Tier tokens; definitions differ |
gen_ai.usage.output_tokens | Output tokens recorded for the response | Output-token meter | Whether reasoning, audio, or image output is split into a separate billable meter |
gen_ai.usage.cache_read.input_tokens and gen_ai.usage.cache_creation.input_tokens | Input tokens served from or written to a provider cache | Cached-read and cache-write usage meters where the provider exposes them | Cache TTL, eligibility, or the price tier applied to each cache class |
gen_ai.usage.reasoning.output_tokens | Output tokens used for reasoning or extended thinking when emitted | Reasoning-token or hidden-output meter if the provider exports it | That every provider exposes reasoning as a separate chargeable quantity |
gen_ai.response.id | Provider response identifier copied into the trace | Possible request-level correlation key | A complete invoice join; usage APIs commonly aggregate into buckets |
trace_id, span_id, service, tenant, workflow attributes | Which application path, team, customer, or agent caused the call | Internal allocation dimensions or a mapping table maintained by your team | Provider billing ownership unless the same project, key, workspace, or account is also exported |
gen_ai.cost.* or vendor cost extensions | A supplied or backend-calculated dollar estimate | Estimate column in your ledger | Invoice truth, discounts, credits, tax, and adjustments; these extensions are not a universal core invoice schema |
Span start/end time and gen_ai.client.* metrics | Timing and aggregated token or duration observations | Provider usage bucket start/end time | Exact clock alignment, late-arriving records, and provider posting time |
The official OTel reference defines input and output usage fields, cache-read and cache-creation fields, reasoning output, model identity, response ID, and token type. [2] The OpenTelemetry implementation guidance shows these fields in traces and exposes token usage as metrics that can be grouped by model. [3] That makes OTel excellent for root-cause analysis and allocation, but it does not turn a span into a billing statement.
The mapping rule: attribution first, billing second
Use a stable internal ledger with one row per observed model span and a second set of rows for provider usage or cost records. Do not overwrite one with the other. A trace row should include the trace and span IDs, the normalized provider, requested and response models, operation name, token counts by type, UTC timestamps, service name, environment, tenant or product, workflow name, and instrumentation version. It should also record whether a value was measured, inferred, or missing.
A provider row should preserve the provider’s native bucket, account or organization, project or workspace, model or line item, meter, quantity, unit, currency, and posted amount. OpenAI’s organization usage reference, for example, exposes bucketed results with input tokens, output tokens, request counts, and optional grouping dimensions such as API key, project, user, source, batch, and line item. It also distinguishes several cache and modality-specific quantities. [5] Those native names should remain available even after you normalize them to an internal vocabulary.
The join is usually not request-for-request. Start with the strongest common keys: provider account, project or workspace, model or SKU, UTC date or time bucket, and meter type. Add API key, user, batch, service tier, or region when both sides contain them. If the provider export is daily and traces are request-level, aggregate the trace side to the same daily grain before comparing. If the provider offers request IDs, use them as a supplemental key, not as an assumption that every invoice row carries them.
Keep three dollar columns: otel_estimated_cost, provider_usage_cost, and invoice_posted_cost. The first is a calculation from trace quantities and a dated rate card. The second is the provider’s usage or cost report, which may be closer to billed usage but still not be the final invoice. The third is the invoice or statement amount. Never label a list-price calculation as “actual spend.”
Cost fields need units and provenance
A cost extension is only useful when its unit is explicit. Datadog’s OTel ingestion documentation illustrates why: it accepts several optional cost attributes, treats supported USD values as dollar amounts, and documents separate nanodollar estimate fields. It also says supplied costs can take precedence over automatic estimates. [4] That is a backend-specific mapping contract, not proof that every OTel consumer or provider recognizes the same fields.
For each cost value, store amount, currency, unit, rate_source, rate_checked_date, pricing_mode, and confidence. A value of 0.01 USD per request is not interchangeable with 10,000,000 nanodollars unless your parser converts the unit deliberately. A token quantity such as 1,200,000 is not a dollar amount. A provider export that reports cents as a decimal string, as Anthropic documents for its cost endpoint, needs a separate conversion step before you compare it with a USD ledger. [7]
The rate source must include the model, meter, region or service tier if applicable, and effective date. OpenAI’s pricing page currently lists GPT-6 Luna at $0.10 per 1 million input tokens, $0.01 per 1 million cached input tokens, and $0.50 per 1 million output tokens; those rates are used only as dated assumptions in the example below, not as a quote for every account or route. [6] Pricing pages can change, and a contract, batch discount, data-residency premium, or enterprise arrangement can supersede public list pricing.
Worked cost example
Assume a US-dollar planning ledger checked on 2026-11-02 for a hypothetical workload using GPT-6 Luna under standard processing. The application emits one GenAI span per model request. There are 10,000 requests in a month, each with 1,200 input tokens and 300 output tokens. Assume no cache hits, no image or audio tokens, no batch discount, no taxes, no credits, and no retry outside the recorded request count. The prices are the public rates stated above.
| Step | Input or assumption | Calculation | Result |
|---|---|---|---|
| 1. Trace volume | 10,000 spans; gen_ai.usage.input_tokens = 1,200 each | 10,000 × 1,200 | 12,000,000 input tokens |
| 2. Trace output | gen_ai.usage.output_tokens = 300 each | 10,000 × 300 | 3,000,000 output tokens |
| 3. Input estimate | $0.10 per 1M input tokens | 12,000,000 ÷ 1,000,000 × $0.10 | $1.20 |
| 4. Output estimate | $0.50 per 1M output tokens | 3,000,000 ÷ 1,000,000 × $0.50 | $1.50 |
| 5. OTel-derived estimate | Input estimate + output estimate | $1.20 + $1.50 | $2.70 |
| 6. Hypothetical posted line | Illustrative invoice line after an assumed $0.40 adjustment | Not a vendor quote; supplied only to show reconciliation | $3.10 |
| 7. Variance | Posted line − OTel-derived estimate | $3.10 − $2.70 | $0.40, or 14.8% of the posted line |
The trace estimate is not “wrong” merely because it differs. Under the stated assumptions it prices observed tokens at public list rates. The illustrative $0.40 variance could represent a different meter, a discount reversal, a credit treatment, a late request, a service-tier difference, or an uninstrumented call. You cannot identify which explanation applies from the two totals alone. Add provider usage rows and invoice line metadata, then test the variance by model, meter, project, day, and service tier.
If cache reads existed, do not silently add them to ordinary input at the ordinary rate. Map the provider’s cache-read meter to the corresponding OTel cache field and apply its own dated rate. If a provider’s aggregate input field already includes cached and cache-write tokens, retain the aggregate and the detailed components separately so you do not double count. OpenAI’s usage reference explicitly describes aggregate input and optional cached, cache-write, audio, image, and output quantities. [5]
A practical reconciliation workflow
- Freeze the schema version. Record the OTel semantic-convention version and instrumentation library version. The old OTel pages now state that GenAI conventions moved to a separate repository, so migration aliases such as older
gen_ai.systemor provider-specific fields should be normalized with a versioned rule. [1] [2] - Capture identity and ownership. Keep provider, requested model, response model, account, project or workspace, service, environment, tenant, and workflow. Never infer a billing project from a free-form service name when a provider export has a real project identifier.
- Normalize quantities, not meanings. Convert token counts to integers and timestamps to UTC, but keep native meter names. Mark whether
input_tokensincludes cached or modality-specific tokens before applying a rate. - Calculate an estimate. Join each observed quantity to a dated rate table. Store the formula and source, such as
input_tokens / 1,000,000 × input_rate. - Aggregate to provider grain. Match the provider’s bucket width and grouping dimensions. A daily provider report cannot be reconciled fairly against a trace sample that covers only one hour.
- Add usage exports. Pull the provider’s usage or cost API, preserving pagination, late-arriving records, null project or key values, and provider-native units. Anthropic documents daily cost buckets, USD cents as decimal strings, usage grouping by model, workspace, service tier, and other dimensions, and a separate treatment for some priority-tier costs. [7]
- Compare and classify variance. Use statuses such as
matched,trace_under-count,trace_over-count,meter_mismatch,rate_mismatch,invoice_adjustment,missing_dimension, andunexplained. Do not force every variance into a token explanation. - Reconcile non-token rows. Tools, retrieval, web search, code execution, embeddings, image generation, storage, taxes, credits, and subscription fees may be outside the text-token calculation. Keep them as separate meters or exclusions.
- Approve with evidence. Finance should be able to open the trace aggregate, provider usage record, rate version, and invoice line that support a posted amount. FinOps guidance likewise treats allocation, token consumption, API usage reports, logs, and cloud billing data as complementary inputs rather than one universal source. [8]
For a broader estimate before this ledger exists, use the LLM observability cost calculator. When the provider statement arrives, use the AI API invoice reconciliation guide and its calculator to compare the estimate with posted usage. If the disagreement is driven by token counts rather than invoice adjustments, the AI Token Cost Calculator guide provides the input, output, and cache arithmetic separately.
Run your scenario
Turn this framework into an editable estimate.
Use dated, source-linked assumptions and see cost per request, successful task and month.
What to exclude or flag instead of guessing
Exclude prompt and response content from the financial join unless there is a documented, privacy-reviewed need. The OpenTelemetry implementation article notes that content capture is opt-in because prompts, tool arguments, and responses can contain sensitive data. [3] Content may explain a large prompt, but it is not a safe billing key.
Flag missing instrumentation, sampling, retries, streaming partials, dropped spans, duplicate exports, and asynchronous tool calls. A trace total can be low because a call was never instrumented. It can be high because the same provider call was exported by both an SDK and a platform integration. Datadog’s documentation specifically warns about mapping and duplicate token sources in some native and SDK export combinations. [4]
Also flag rate ambiguity. “Input tokens” may mean ordinary input, all input including cache writes, or a provider-specific aggregate. “Cost” may mean list-price estimate, provider usage cost, posted invoice line, or invoice total after credits and taxes. Put the definition beside the number. A clean dashboard with an undefined meter is not a reconciliation.
FAQ
Are OpenTelemetry LLM cost fields the same as invoice fields?
No. OTel core fields describe telemetry about a model operation, especially identity, token usage, timing, and trace context. An invoice can include discounts, credits, regional or service-tier pricing, taxes, minimums, and non-token meters that are not present in the span. Treat OTel dollars as estimates unless a provider statement confirms them.
Which OTel fields should I capture first for cost mapping?
Start with gen_ai.provider.name, gen_ai.request.model, gen_ai.response.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.response.id, UTC span time, and ownership attributes such as service, environment, tenant, project, and workflow. Add cache-read, cache-creation, reasoning, audio, image, and batch dimensions when your provider exposes corresponding meters.
Can I join a trace directly to a provider invoice row?
Sometimes, but usually not at one-request granularity. Use a response or request ID when the provider exports it. Otherwise aggregate traces to the provider’s time, account, project, model, and meter dimensions. Record unmatched rows and late-arriving usage instead of manufacturing a request-level join.
Should cached input tokens be added to ordinary input tokens?
Not automatically. First determine whether the provider’s aggregate input field already includes cached or cache-write tokens. Then preserve detailed cache fields and apply the provider’s cache rate. Adding cache reads to ordinary input without checking the provider definition is a common double-counting error.
How should I handle reasoning tokens?
Keep reasoning output as a separate quantity when the instrumentation and provider export expose it. Apply a reasoning-specific rate only when the provider bills it separately. If the provider includes it in output, do not add it again. The OTel registry defines a reasoning-output field, but field availability and billing treatment remain provider-specific. [2]
Is a vendor’s gen_ai.cost.* extension an invoice truth field?
No. It may be a useful USD estimate or an explicitly supplied cost for an observability backend. Check the extension’s unit, currency, precedence, and fallback behavior. Keep it in otel_estimated_cost and reconcile it to provider usage and invoice columns separately. [4]
How often should the crosswalk be checked?
At every OTel semantic-convention or instrumentation upgrade, and whenever a provider changes a model, pricing tier, usage API, cache policy, or invoice format. Run a small known-volume test after each change, then compare daily aggregates before trusting a monthly total.
Primary sources checked
Pricing and product behavior can change. These sources support the time-sensitive claims in this guide; verify current provider terms before committing spend.
- OpenTelemetry Generative AI semantic conventions moved notice
- OpenTelemetry GenAI attribute registry
- Inside the LLM Call: GenAI Observability with OpenTelemetry
- Datadog OpenTelemetry instrumentation for Agent Observability
- OpenAI organization usage API reference
- OpenAI API pricing
- Anthropic Usage and Cost API
- FinOps for AI