Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

Which pricing key your model lands under

Predict the catalog key a request resolves to, why one model carries seven prices, and the six typed reasons a row has none.

ExplanationLast updated

On this page

A model id is not a price. deepseek-r1 appears in the committed price table seven times, at rates that differ by a factor of nearly seven, because it is sold through seven different doors:

Catalog key Input, per million
deepseek/deepseek/deepseek-r1 $0.55
openrouter/openrouter/deepseek/deepseek-r1 $0.55
vercel_ai_gateway/vercel_ai_gateway/deepseek/deepseek-r1 $0.55
azure_ai/azure_ai/deepseek-r1 $1.35
snowflake/snowflake/deepseek-r1 $1.35
fireworks_ai/fireworks_ai/accounts/fireworks/models/deepseek-r1 $3.00
replicate/replicate/deepseek-ai/deepseek-r1 $3.75

So a gateway asked “what does deepseek-r1 cost?” has to answer “through which upstream?” first, or it is guessing. This page is how that question gets answered, so you can predict your key before you send anything, and diagnose it when a row comes back with no dollar at all.

The rule in one sentence

The catalog namespace comes from the upstream’s descriptor, not from the wire dialect.

78 descriptors ride the openai wire. Eight of them declare no catalog namespace at all, because their zero is asserted rather than looked up; the other 68 bind 68 distinct namespaces, no two sharing one. Two Azure products ride that same driver and bind azure/ and azure_ai/ separately. The wire says how to talk; the descriptor says whose price list applies.

A binding is a claim about which prices apply, and it is not the same claim as a tested cell. All but ten of the 95 built-in provider descriptors carry at least one endpoint the generated matrix gates with a named test, asserting the ledger row’s own provider and endpoint stamps against a mock shaped like that provider; the rest print declared (no e2e gate), which the matrix calls not a support claim rather than hiding. The verification is against mocks, not live endpoints.

The consequence you can check:

PREFIX     MODEL         RESULT    KEY / REASON
deepseek   deepseek-r1   PRICED    deepseek/deepseek/deepseek-r1    in=$0.55/M out=$2.19/M
azure_ai   deepseek-r1   PRICED    azure_ai/azure_ai/deepseek-r1    in=$1.35/M out=$5.40/M
openai     deepseek-r1   UNPRICED  model_unknown

The third line is the point of the design. Pointing a DeepSeek model at an OpenAI upstream produces no figure, because no candidate key under openai/ matches. The alternative, a suffix search across every provider, would have found a rate, and it would have been someone else’s.

The walk

How a model string becomes a catalog key, or a named reasonResolution starts from four inputs: the upstream descriptor's catalog prefix, the model string that was sent, the endpoint kind, and a region class where the provider has one. It then tries candidate keys in order. First, for region-qualified providers, the prefix followed by the region and the model. If region-qualified entries exist for this model but none for the requested region, the answer is region unknown, and a region-neutral rate is not substituted. Next the prefix followed by the model, then the model string as a literal key, then a suffix match restricted to keys under the same prefix. Three of those four forms are scoped to this upstream's own prefix; the bare literal-key step is not, and what keeps another provider's rate out of reach there is that the price artifact holds no key without a prefix on it. The first candidate that exists and serves the requested endpoint wins; if it exists but lacks the rate units the endpoint bills in, the answer is unit missing. If keys for the model exist but none serve the endpoint, the answer is endpoint unpriced. If no key exists at all, the answer is model unknown. Each of the four outcomes is recorded as a typed reason on the row rather than as a zero dollar figure.Inputscatalog_prefix (descriptor)model string (tier/request)endpoint · region classCandidate keys, in order1 · prefix/region.model2 · prefix/region/model3 · prefix/model4 · model (literal key)5 · prefix/…/model (suffix, same prefix)every candidate starts with this upstream's prefixPRICEDkey + snapshotThe region ruleif region-qualified keys exist for this model and none matches your region, resolution stops at region_unknown — a region-neutral rate is not substitutedOtherwise: one of four typed reasons, and no dollarmodel_unknownregion_unknownendpoint_unpricedunit_missing

Read it left to right. Four inputs go in: the descriptor’s catalog prefix, the model string as sent, the endpoint kind, and a region class where the provider has one. Candidate keys are tried in order, and the first that both exists and serves the requested endpoint wins.

Order Candidate What it is for
1 prefix/region.model the region-qualified form, for providers whose catalog is region-keyed
2 prefix/region/model the path-shaped variant of the same
3 prefix/model the plain form
4 model the string as a literal key, for a model id that already carries its own prefix
5 prefix/…/model a suffix match, restricted to keys under the same prefix

All but one of the five candidates begin with this upstream’s own prefix, and that restriction is the mechanism behind the rule above: a bare model id cannot reach a neighbouring provider’s key, however familiar the id looks. No key in the artifact is unprefixed, so there is nothing for a bare id to hit. The exception is step 4, which tries the string exactly as you sent it: a tier whose model field already spells out another provider’s namespace resolves under that namespace. That is the one door across, and you open it by typing it.

The same restriction is why one descriptor covers nothing. openai_compatible binds the catalog prefix openai_compatible, which is not one of the 107 prefixes in the vendored litellm@2026-07-14#179affb price file, so its prefixed candidates are keys the artifact does not hold and the coverage generator counts the descriptor as covering nothing rather than inflating a number. Rows through it come back model_unknown, which is the escape hatch working as configured.

Step 5 is why most keys look doubled. The vendored catalog namespaces each provider’s models by provider, and many providers publish model ids that already contain their own name, so the stored key for a Groq model is groq/groq/llama-3.3-70b-versatile. You send llama-3.3-70b-versatile; step 3 misses; step 5 finds it inside groq/. When two keys under the prefix both match, the one with fewer path segments wins, then alphabetical order, so the choice is stable across catalog updates.

One kind of request probes ahead of step 1. An image call carrying a size or a quality tries the qualified forms first, prefix/quality/size/model and then prefix/size/model, because the per-image catalog is keyed by those dimensions. If neither exists the walk continues at step 1, and a model that has only qualified forms then has nothing left to hit.

Where the model string comes from

Step 1 needs a model string, and it is not always in the JSON body. Three places are read, in a fixed order, and the body wins.

Where Consulted Example
the body always first, from the dialect’s own parse or a multipart model form field {"model": "gpt-4o"}, or Stability’s generate/sd3 form
the URL path only when the body yielded none /v1beta/models/{model}:generateContent, /model/{model}/converse
the URL query only when the body and path yielded none /v1/listen?model=nova-3

The multipart form field is the newest of the three, and Stability is why the order is worth stating. Its generate/sd3 path serves seven catalog rows priced from $0.035 to $0.065, so a model read from the path would be the same string for all seven and six of them would carry a confidently wrong dollar. A request whose model is in none of the three is ledgered with an absent model and resolves model_unknown, which is a stated absence rather than a guess.

The region rule

One step is not a “try next” but a stop.

That trade is visible in the numbers. The same Bedrock model, at three region classes:

PREFIX   MODEL                                     REGION  RESULT    KEY / REASON
bedrock  anthropic.claude-3-5-haiku-20241022-v1:0  us      PRICED    bedrock/us.anthropic.…  in=$0.80/M
bedrock  anthropic.claude-3-5-haiku-20241022-v1:0  eu      PRICED    bedrock/eu.anthropic.…  in=$0.25/M
bedrock  anthropic.claude-3-5-haiku-20241022-v1:0  apac    UNPRICED  region_unknown

A region-neutral key for that model exists in the catalog and is not used. Between the two priced regions the rate differs by 3.2×, which is the size of the error the rule prevents.

The four reasons the walk runs out

When the walk finds nothing usable, the row carries "unpriced": true and a typed reason. Four of them come out of the walk itself. They are different diagnoses and they want different fixes.

Reason What happened Usually means
model_unknown no key for that model under this prefix wrong descriptor, or a model the catalog does not carry
region_unknown region-qualified keys exist, none for your region that rate is not published for your region
endpoint_unpriced keys exist for the model, none serves this endpoint calling a chat model at an embeddings endpoint
unit_missing a key matched but lacks the units the endpoint bills in an image call with no size or quality

One request each, from the committed snapshot:

PREFIX   MODEL                                     ENDPOINT    REGION  REASON
openai   deepseek-r1                               chat                model_unknown
bedrock  anthropic.claude-3-5-haiku-20241022-v1:0  chat        apac    region_unknown
openai   gpt-4o                                    embeddings          endpoint_unpriced
openai   dall-e-3                                  images              unit_missing

None of the four produces usd: 0. A zero would be a claim, and the claim would be wrong; the row records the absence instead, and downstream totals exclude it and say so.

The two that are not pricing misses

The typed vocabulary holds six values, and the other two do not come from this walk at all. They come from the async settlement plane, where a job is submitted and costed later.

Reason What happened What clears it
settlement_pending a job was submitted and the provider has not costed it yet a settlement companion row, minutes later
settlement_terminal the job reached a terminal state that produced no priceable result nothing; that money will not become known

No pricing action clears either. A price-table edit fixes a model_unknown; it does nothing for a job the provider has not finished, and nothing at all for one that failed. So the two carry their own reason strings rather than borrowing model_unknown, and a work queue built from the unpriced reasons should leave them out: counting them as catalog gaps reports a gap that does not exist.

Predicting your key before you send

Two things to do, in this order.

Check the descriptor’s prefix. It is in the provider reference, and it is what the candidate keys are built from.

Ask the binary for the rate, with the prefix attached:

$ tokentriage prices show groq/llama-3.3-70b-versatile
model:      groq/llama-3.3-70b-versatile
resolved:   groq/groq/llama-3.3-70b-versatile
provider:   groq
snapshot:   litellm@2026-07-14#179affb
source:     litellm  fetched 2026-07-14  generator pricegen v2
upstream_sha256: 179affb29103fed8b02a83e22e93209c2c65a393cbf9dd785dbaec0f8748d6f7

RATE         USD / TOKEN          USD / 1M TOKENS
input        5.9e-07              $0.5900
output       7.9e-07              $0.7900
cache_read   (fallback = input)   $0.5900
cache_write  (fallback = input)   $0.5900
reasoning    (fallback = output)  $0.7900

The snapshot line and the digest are the artifact’s, and they are the same on any machine holding this build; the model string is yours.

Diagnosing a key that came out wrong

Read three fields on the row and the answer is usually immediate:

"http": { "provider": "groq", "endpoint": "chat" },
"cost": { "pricing_key": "groq/groq/llama-3.3-70b-versatile",
          "pricing_snapshot": "litellm@2026-07-14#179affb", "estimated": false }
What you see What it means
http.provider is not what you expected a routing decision sent the request elsewhere; check decision.tier and decision.upstream on the same row
pricing_key starts with the wrong prefix the upstream names a descriptor you did not intend, or the tier’s model string spells out a namespace itself; two Azure products and twelve Vertex namespaces are the usual culprits
pricing_key is empty, no unpriced flag a declared-free upstream, working as configured
unpriced: true with a settlement reason an async job; the dollar is on a later row, or it is not coming
unpriced: true with any other reason read unpriced_reason and use the table above

The catalog is upstream data

The vendored price file carries 2,889 priced model keys across 107 prefixes under the stamp litellm@2026-07-14#179affb. That is a statistic about a vendored price table, not a claim about what TokenTriage can reach: a key existing does not mean a descriptor binds its prefix, and a descriptor binding a prefix does not mean it can reach every model priced inside it.

It also carries defects, and the copy is not edited to hide them. Under oci/ the artifact prices openai.gpt-5, -mini and -nano, which Oracle does not document on OCI, while the two OpenAI models Oracle does document there, gpt-oss-120b and gpt-oss-20b, carry no price at all. Call either of those two through an OCI upstream and the row comes back model_unknown, because the catalog holds no key for it. Silently correcting our copy would make it disagree with its source and bury the defect rather than publishing it.

Where operator overrides fit

A pricing override you have configured is consulted before the catalog for token-billed endpoints, and a row it prices carries an operator@ stamp instead of the catalog snapshot. So a pricing_snapshot that does not start with litellm@ means the figure came from your own rate table, which is what you want to see if you negotiated one.

Overrides carry token rates, so they are consulted for token-billed endpoints and skipped for the per-image, per-second and per-character ones, which fall straight through to the catalog.