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.

● First-party sources checked▧ Original calculation examples◌ Estimate, not a vendor quote

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 familyWhat it tells your applicationInvoice-side counterpart or join aidWhat remains unproven
gen_ai.provider.nameProvider identified by instrumentationProvider account, contract, or billing source selected for the reconciliation runThat the provider label matches the legal billing entity or marketplace reseller
gen_ai.request.model and gen_ai.response.modelRequested model and the model that returned the responseProvider model, SKU, deployment, or line-item descriptionRegional SKU, dated snapshot, service tier, or routed model unless exported separately
gen_ai.usage.input_tokensInput tokens recorded for the GenAI operationInput-token meter in a provider usage reportWhether the number includes cached, cache-write, audio, image, or Scale Tier tokens; definitions differ
gen_ai.usage.output_tokensOutput tokens recorded for the responseOutput-token meterWhether 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_tokensInput tokens served from or written to a provider cacheCached-read and cache-write usage meters where the provider exposes themCache TTL, eligibility, or the price tier applied to each cache class
gen_ai.usage.reasoning.output_tokensOutput tokens used for reasoning or extended thinking when emittedReasoning-token or hidden-output meter if the provider exports itThat every provider exposes reasoning as a separate chargeable quantity
gen_ai.response.idProvider response identifier copied into the tracePossible request-level correlation keyA complete invoice join; usage APIs commonly aggregate into buckets
trace_id, span_id, service, tenant, workflow attributesWhich application path, team, customer, or agent caused the callInternal allocation dimensions or a mapping table maintained by your teamProvider billing ownership unless the same project, key, workspace, or account is also exported
gen_ai.cost.* or vendor cost extensionsA supplied or backend-calculated dollar estimateEstimate column in your ledgerInvoice truth, discounts, credits, tax, and adjustments; these extensions are not a universal core invoice schema
Span start/end time and gen_ai.client.* metricsTiming and aggregated token or duration observationsProvider usage bucket start/end timeExact 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.

StepInput or assumptionCalculationResult
1. Trace volume10,000 spans; gen_ai.usage.input_tokens = 1,200 each10,000 × 1,20012,000,000 input tokens
2. Trace outputgen_ai.usage.output_tokens = 300 each10,000 × 3003,000,000 output tokens
3. Input estimate$0.10 per 1M input tokens12,000,000 ÷ 1,000,000 × $0.10$1.20
4. Output estimate$0.50 per 1M output tokens3,000,000 ÷ 1,000,000 × $0.50$1.50
5. OTel-derived estimateInput estimate + output estimate$1.20 + $1.50$2.70
6. Hypothetical posted lineIllustrative invoice line after an assumed $0.40 adjustmentNot a vendor quote; supplied only to show reconciliation$3.10
7. VariancePosted 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

  1. 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.system or provider-specific fields should be normalized with a versioned rule. [1] [2]
  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.
  3. Normalize quantities, not meanings. Convert token counts to integers and timestamps to UTC, but keep native meter names. Mark whether input_tokens includes cached or modality-specific tokens before applying a rate.
  4. 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.
  5. 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.
  6. 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]
  7. Compare and classify variance. Use statuses such as matched, trace_under-count, trace_over-count, meter_mismatch, rate_mismatch, invoice_adjustment, missing_dimension, and unexplained. Do not force every variance into a token explanation.
  8. 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.
  9. 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.

AI API invoice reconciliation calculator

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.

  1. OpenTelemetry Generative AI semantic conventions moved notice
  2. OpenTelemetry GenAI attribute registry
  3. Inside the LLM Call: GenAI Observability with OpenTelemetry
  4. Datadog OpenTelemetry instrumentation for Agent Observability
  5. OpenAI organization usage API reference
  6. OpenAI API pricing
  7. Anthropic Usage and Cost API
  8. FinOps for AI

About the Author

Zunair Ali Syed is a doctoral researcher in Management Sciences at Minhaj University Lahore and a research consultant with experience in academic research and editing since 2011. He runs QuickEduHelp, with professional interests in digital learning, research methods, and evidence-based academic support.