Model catalog and pricing
The SDK never computes a price. It reports token counts, a provider, a model identifier and a timestamp, and the server turns those into a figure using an effective-dated catalog.
When pricing happens
Section titled “When pricing happens”Pricing runs once, in the worker, as the batch is processed. The rate is
selected using the call’s own timestamp, not the time the batch happened to
be processed, so a delayed batch is still priced at the rate that was in force
when the call was made. The result is written to the row’s cost_usd column
along with the catalog’s own figure, the price row that produced it, the reasons
the result is partial, and a status.
Every currency figure in the dashboard is then a sum of those stored values. The read path does no arithmetic against the catalog.
The pricing pipeline, step by step
Section titled “The pricing pipeline, step by step”1. Normalize the provider
Section titled “1. Normalize the provider”The reported provider is lowercased, then aliased.
| Reported | Becomes |
|---|---|
amazon-bedrock, aws, aws-bedrock | bedrock |
gateway, vercel, vercel-ai-gateway | The creator half of the creator/model identifier |
| Anything else | Itself |
2. Resolve an alias
Section titled “2. Resolve an alias”The pair (provider, model) is looked up in the alias table. The model
identifier is matched verbatim, lowercased, which is why the raw model string
your code sent is kept untruncated on the row.
When the provider is unknown or missing ("", unknown, litellm, proxy,
none, null) the lookup falls back to matching on the model identifier alone.
That fallback succeeds only when every candidate points at the same canonical
model. If several candidates disagree, the result is unpriced with the reason
ambiguous_model rather than a guess.
Where one canonical model has several pricing channels, a first-party channel
wins over a reseller. The order is anthropic-api, openai-api,
google-ai-studio, google-api, xai-api, deepseek-api, mistral-api,
amazon-api, meta-api, moonshot-api, minimax-api, zai-api,
alibaba-api, aws-bedrock, aws-bedrock-geo.
3. Pick the effective price row
Section titled “3. Pick the effective price row”Prices are intervals, not values. Each row has an effective_from and an
optional effective_to, and the call’s own timestamp selects the row. Two calls
to the same model on different days can legitimately price differently.
Region resolution tries your deployment’s region, then *, then global.
No interval covering the timestamp means unpriced with the reason
no_effective_price. The canonical model is still recorded, so you can see that
the model was recognized and only the rate was missing.
4. Work out the billable input
Section titled “4. Work out the billable input”Providers differ in whether the headline input count already includes cached tokens. The catalog carries that as a per-model rule rather than assuming.
| Rule | Effect |
|---|---|
input_includes_cache_read | Cache-read tokens are subtracted from billable input before the input rate is applied |
input_includes_cache_write | Cache-write tokens are subtracted as well |
If the subtraction would go negative, billable input becomes zero and the reason
cache_read_exceeds_input or cache_write_exceeds_input is recorded. The row is
priced, but it is flagged partial so you know the arithmetic was clamped.
5. Choose the rates
Section titled “5. Choose the rates”| Rate | Applied to |
|---|---|
input_per_mtok | Billable input tokens |
output_per_mtok | Output tokens |
cache_read_per_mtok | Cache-read tokens |
cache_write_5m_per_mtok | Cache writes explicitly attributed to a 5 minute TTL |
cache_write_1h_per_mtok | Cache writes explicitly attributed to a 1 hour TTL |
batch_input_per_mtok | Input tokens, when the call was a Batch API call |
batch_output_per_mtok | Output tokens, when the call was a Batch API call |
All rates are USD per million tokens.
Batch rates are separate rows, not a discount factor. A call whose batch
flag is true uses batch_input_per_mtok and batch_output_per_mtok if the
catalog has both. If either is missing, the standard rates are used and the
reason batch_rate_unavailable is recorded, so the figure is an over-estimate
and says so. Cache rates are not swapped for a batch call.
Cache writes are priced by TTL. Anthropic reports 5 minute and 1 hour cache
writes separately, and they have different rates. Where a provider reports only a
total cache-write count, the remainder after subtracting the TTL-attributed
counts is charged at the cheaper of the two available rates, and the reason
cache_write_ttl_ambiguous is recorded when those rates differ. A total smaller
than the sum of its parts records cache_write_details_exceed_total.
6. Apply a long-context multiplier
Section titled “6. Apply a long-context multiplier”Some models charge more once the prompt passes a threshold. Where the catalog
carries a long_context rule and the total input count exceeds its
threshold, the input and output multipliers are applied. The multiplier also
applies to cache-read and cache-write rates, because those are input-side.
7. Round
Section titled “7. Round”The result is quantized to eight decimal places, half up.
8. Resolve against gateway evidence
Section titled “8. Resolve against gateway evidence”One case overrides the catalog: an OpenRouter chat.completions call whose
response carried usage.cost, with the SDK’s own fixed provenance string. That
amount becomes the effective cost and the status is priced. The catalog’s own
figure is kept alongside it in catalog_cost_usd.
No other reported cost overrides the catalog. An upstream inference cost from OpenRouter is retained as provenance and never combined with anything.
Cost status
Section titled “Cost status”Every row carries one.
| Status | Meaning | Contributes to a dashboard total? |
|---|---|---|
priced | The catalog produced a figure with no caveats, or a qualified gateway amount was used | Yes |
partial | A figure was produced, but at least one reason was recorded | Yes, and it may be low or high |
unpriced | No figure. cost_usd is null | Contributes zero |
reported | Legacy rows only. No new row gets this status | Yes |
Catalog reasons
Section titled “Catalog reasons”The reason list is the audit trail for a partial or unpriced row.
| Reason | What happened |
|---|---|
unknown_model | No alias matched the provider and model pair |
ambiguous_model | The provider was unknown and the model name matched more than one canonical model |
no_effective_price | The model is known, but no price interval covers the call’s timestamp |
catalog_unavailable | The catalog could not be loaded when the batch was processed |
missing_input_tokens | The provider reported no input count. Treated as zero |
missing_output_tokens | The provider reported no output count. Treated as zero |
input_rate_unavailable | There are billable input tokens but no input rate |
output_rate_unavailable | There are output tokens but no output rate |
cache_read_rate_unavailable | Cache reads occurred but no cache-read rate exists |
cache_write_5m_rate_unavailable | 5 minute cache writes occurred but no rate exists |
cache_write_1h_rate_unavailable | 1 hour cache writes occurred but no rate exists |
cache_write_rate_unavailable | Cache writes occurred and neither TTL rate exists |
cache_write_ttl_ambiguous | Untagged cache writes were charged at the cheaper of two differing rates |
cache_write_details_exceed_total | The per-TTL counts add up to more than the reported total |
cache_read_exceeds_input | Cache reads exceeded the headline input count. Billable input clamped to zero |
cache_write_exceeds_input | Cache writes exceeded billable input. Clamped to zero |
batch_rate_unavailable | A batch call was priced at standard rates because no batch rate exists |
uncaptured_fees | The model carries charges this catalog does not model, so the figure is a floor |
The unpriced bucket
Section titled “The unpriced bucket”An unrecognized model produces no cost and lands in the unpriced coverage
bucket. It is still a captured call: it counts toward the call total, toward the
monthly allowance, toward your error rate, and it appears in Traces with its
tokens and latency. It just contributes nothing to spend.
This is the single most common reason a spend figure looks too low, and it is why the dashboard reports coverage rather than presenting a floor as a fact.
Diagnosing a total that looks too low
Section titled “Diagnosing a total that looks too low”-
Read the coverage line under Spend by model. On Performance, when coverage is incomplete the table says
A lower bound. N calls carry no price.If that line is absent, every call in the window is priced and the total is not the problem. -
Find the model. Set Group by to Model on the timeline, or read the Spend by model table. A model with a healthy call count and a spend of
$0.00is unpriced. The identifier shown is exactly the string your code sent. -
Check whether it is the model or the provider. Open a trace on that model and read the span inspector. Cost reading
Unpricedwith a sensible Model and Provider points at a catalog gap. A Provider that is blank,unknownor a proxy name points at an ambiguous-model resolution instead, and is fixed on your side by naming the provider when you wrap the client. -
Check tokens, not just cost. A model priced correctly but reporting no token counts also produces a low total. The most common cause is a streaming OpenAI chat call with
METERGRAPH_PATCH_STREAM_USAGE=0, which leaves the provider reporting no usage at all. See Errors. -
Check whether it is a batch call. A Batch API call priced at standard rates because the catalog has no batch rate is an over-estimate, not an under-estimate. Batch API’s expected relative cost says
Unavailable: the catalog has no batch price for a contributing modelwhen this applies to a probed model.
If a model you use is missing from the catalog, that is a catalog gap on the Metergraph side rather than something you can configure. Report it with the exact model identifier from step 2.
Which models are covered
Section titled “Which models are covered”The catalog is a database table, edited by platform administrators against provider pricing pages, with each row carrying its own source URL and the date it was last checked. It changes between releases, and a printed list here would be stale within weeks.
Check coverage in your own dashboard instead, using the four steps above. The question that matters is not “which models does Metergraph know” but “is every model my traffic uses priced”, and the coverage line answers exactly that for your workspace.
Coverage is broad across the first-party APIs of Anthropic, OpenAI, Google, xAI, DeepSeek, Mistral, Amazon, Meta, Moonshot, MiniMax, Z.ai and Alibaba, plus AWS Bedrock and the Vercel AI Gateway. That is the set of pricing channels the resolver knows how to prefer, not a guarantee that every model on each is present.
What the catalog stores
Section titled “What the catalog stores”For completeness, because it explains the vocabulary above.
| Table | Holds |
|---|---|
model_catalog | One row per canonical model: publisher, display name, family, lifecycle (preview, active, limited, legacy, retired), release date, context window, max output, capabilities, source URL, last-checked date |
model_aliases | The many identifiers that resolve to one canonical model, each with its pricing channel and its per-model rules |
model_prices | Effective-dated rates per model, channel, region and service tier. USD only. A row must carry at least one of an input, output or cache-read rate |
Editing any of these requires the platform-administrator role. There is no per-workspace price override.
Related
Section titled “Related”- Captured fields for the cost fields on a row
- The dashboard for where each figure appears
- Limits and allowances
- Errors