Pricing & Tiers
Pricing answers one question at settlement: what did this exchange cost, given the provider, the upstream model and the normalized usage the channel extracted.
It happens inside the engine, at settlement, so budgets and the observer see the same number. In v3 the engine handed usage up and an application layer priced it again, which produced two time semantics, two model matchers and two answers to “what if there is no price”. v4 has one of each.
Three tables, in one shape: a price_rules row selects a model, its
price_rates rows price each metric, and its price_tiers rows adjust the
token ladder by prompt size and service tier.
Rule Selection
Section titled “Rule Selection”A rule fits when
- its provider is the exchange’s provider, or is unset (a global rule);
- its
modelPatternglob matches the upstream model name; - its
operationis the request’s, or is unset (all operations).
Provider rules precede global rules. Within one scope, the lowest
(priority, id) wins.
| Field | Meaning |
|---|---|
providerId | null is a global rule |
modelPattern | * / ? glob against the upstream model name |
operation | null covers every operation of the matched model |
priority | lower first; the id breaks ties |
currency | Must be USD; all prices and settlements use USD |
enabled |
No rule at all
Section titled “No rule at all”The exchange is unpriced: it costs nothing, it still settles, and its usage carries
{"dimensions": {"unpriced": "true"}}That is a signal, not a refusal. An operator wants to know that a model is being served for free; they do not want a request rejected because nobody has entered a price yet.
Rate Rows
Section titled “Rate Rows”A rate row is the base price of one metric.
| Field | Meaning |
|---|---|
metric | a built-in key, or any custom one a channel emits |
unit | token, count, second or character |
unitQuantity | the positive denominator: 1,000,000 tokens, 1 image, 60 seconds |
value | the non-negative price for unitQuantity units, in the rule’s currency |
conditions | null is the fallback row; otherwise a non-empty object of dimension names to scalars, all of which must match |
priority | lower first among rows of the same metric |
For one metric, conditional rows are tried in (priority, id) order and the
first whose conditions all match the settlement’s dimensions wins; otherwise
the fallback row applies. A selected conditional rate replaces the base
rate, it does not compose with it.
[{"metric":"input_tokens", "unit":"token","unitQuantity":"1000000","value":"0.40"}, {"metric":"output_tokens", "unit":"token","unitQuantity":"1000000","value":"1.60"}, {"metric":"image_outputs", "unit":"count","unitQuantity":"1","value":"0.04", "conditions":{"quality":"hd","size":"1024x1024"},"priority":0}]The rule that stops double billing
Section titled “The rule that stops double billing”Rates must consume disjoint quantities. Cached input is removed from ordinary input; a reasoning or media token subset must not be charged again when it is already billed through an aggregate. Per-image and per-token output are alternative bases, unless the provider really charges both.
A generic tool_calls row and a specific web_searches row must not both bill
the same call. And a client declaring a tool is not a billable server-side
execution.
The Metrics
Section titled “The Metrics”Tokens — conventionally per 1,000,000:
input_tokens output_tokens cached_input_tokenscache_creation_tokensreasoning_tokens image_input_tokens image_output_tokensaudio_input_tokens cached_audio_input_tokens audio_output_tokensvideo_input_tokens video_tokensCache writes use cache_creation_tokens. A rate condition such as {"ttl_seconds":43200} selects a 12-hour write price; omit this condition for the fallback price. Each reported write bucket is priced separately. Cache reads remain a single cached_input_tokens count, with no TTL. Tier overrides use cacheCreationPerMillion: [{"ttlSeconds":43200,"price":"5"}]; an entry with ttlSeconds: null is the fallback. Prices are per million tokens, not per token-second.
Counts, seconds and characters:
image_outputs video_outputs audio_seconds video_seconds audio_characterssearch_units web_searches web_fetches file_searchescode_interpreter_sessions tool_calls requestsA metric outside this list is still priced as long as a rate row names it. The list is what the built-in keys are, not a filter.
requests is the per-request fee: one request is charged per priced
exchange.
A price_tiers row overrides token prices only, always per million.
| Field | Meaning |
|---|---|
serviceTier | null makes it a context tier; otherwise standard, priority, flex, batch, … |
minPromptTokens | a non-negative inclusive threshold; default 0 |
multiplier | for a service tier: what to multiply the inherited price by. Context-only rows leave it unset |
*_per_million | an explicit price for one token kind |
The explicit columns are input, output, cache_read,
cache_creation_per_million, reasoning, image_input,
image_output, audio_* and video_*.
Prompt length counts input plus cache reads and cache writes, once.
Composition, per token kind
Section titled “Composition, per token kind”- The base rate — the
price_ratesrow for that metric, conditional rows first. - The context tier — the highest reached
minPromptTokensamong rows with no service tier. Its explicit prices replace the base for the kinds it sets. - The service tier — the highest reached threshold among rows naming the
actual service tier. An explicit price there wins outright; otherwise
the context-adjusted base is multiplied by that row’s
multiplier(default 1).
Equal thresholds break on the lower (priority, id). null inherits; zero is
explicitly free, not missing.
A multiplier never applies to tool or media counts — only to token prices.
Two special cases
Section titled “Two special cases”- Reasoning is a subset of output. When a reasoning price exists, the
reasoning tokens are subtracted from
output_tokensand charged separately. When it does not, they stay inside output and are charged once. - A cache read with no price inherits the input price. Every other kind with no price is free. That asymmetry is deliberate: a cache read is unambiguously an input token, while a missing audio or video price is a configuration gap the operator should see as zero.
Requested versus served
Section titled “Requested versus served”The tier that is charged is the one the response actually reported, not the
one the request asked for. A priority request the provider downgrades settles
at the downgraded row.
Everything Else
Section titled “Everything Else”Every metric that is not a token kind is charged as
amount × value / unitQuantityand is never multiplied by a service tier.
Editing Prices
Section titled “Editing Prices”curl -s http://127.0.0.1:8787/admin/api/price-rules -H "Authorization: Bearer $GPROXY_KEY"curl -s http://127.0.0.1:8787/admin/api/price-rates -H "Authorization: Bearer $GPROXY_KEY"curl -s http://127.0.0.1:8787/admin/api/price-tiers -H "Authorization: Bearer $GPROXY_KEY"Each family takes the usual five routes plus a batch, and a batch is one revision commit however many rows it names.
The bundled catalogue fills them in for the models this release knew about:
curl -s -X POST http://127.0.0.1:8787/admin/api/default-model-catalog/apply-prices \ -H "Authorization: Bearer $GPROXY_KEY" -H 'content-type: application/json' \ -d '{"providerId":"…","modelIds":["gpt-4o-mini"],"overwrite":false}'Without a provider, the catalogue’s own glob and priority are used, so one rule
prices that model wherever it is served; with a provider, the literal name
becomes the pattern at priority zero. overwrite: false is what makes
re-applying safe: a rule an operator edited keeps its edit and is reported as
skipped.
A bad pricing row is dropped with a warning at assembly and does not block the snapshot. One mistyped decimal must not take down an instance that is otherwise serving traffic.
Budgets Spend the Result
Section titled “Budgets Spend the Result”A budget is a quotas row whose metric is cost in USD. Admission hands the
engine the caller’s chain — [api_key?, user, team?, org?] —
and every enabled budget of any owner in the chain applies.
Costs settle after the upstream call. In-flight and concurrent requests may exceed a budget. See Permissions, rate limits, and budgets for ownership and checks.
Token Estimation
Section titled “Token Estimation”When a billable response carries no usage at all, the engine counts the tokens
itself and marks the record estimated = true. Counting uses a local
vocabulary: the tiktoken encodings for GPT-family models, otherwise the
vocabulary the model’s catalogue row names, otherwise the bundled DeepSeek one.
curl -s http://127.0.0.1:8787/admin/api/tokenizer-vocabs -H "Authorization: Bearer $GPROXY_KEY"curl -s -X POST http://127.0.0.1:8787/admin/api/tokenizer-vocabs \ -H "Authorization: Bearer $GPROXY_KEY" -H 'content-type: application/json' \ -d '{"repo":"…","filename":"tokenizer.json","modelId":"…","setAsDefault":true}'A fetch downloads the file through the configured file storage, then inserts the row and points the model and the default at it — the row and both pointers in one revision commit. The bytes land before the row: a row pointing at an object that was never written would fail every reload, while an object with no row is only wasted space.
Without file storage the whole family answers “unsupported”: there is nowhere to put the bytes.