Service Tiers¶
Some providers sell alternative handling lanes for the same model: a faster, priority lane at a premium, a cheaper, best-effort flex lane and, on one OpenAI model, a much faster ultrafast lane at six times the standard price. The toolkit exposes them all through one provider-agnostic parameter:
agent = Agent(model="gpt-5.6-terra", service_tier="priority")
result = await agent.run("Summarize this filing")
The headline rule: you are billed for the tier that actually served the request, not the tier you
asked for. If the provider quietly falls back to standard handling, the toolkit prices that request
at standard rates and sets AgentResult.tier_downgraded.
Per-provider semantics¶
| Provider | What a tier does | Request carries | Served tier read from |
|---|---|---|---|
| OpenAI | "priority" = Fast mode (higher tokens/sec, priority handling); "flex" = the cheaper best-effort lane; "ultrafast" = Ultrafast mode (gpt-6-astra only) |
service_tier="fast" / "flex" / "ultrafast" |
response.service_tier on the completed response (echoes "priority" or "fast", "flex", "ultrafast") |
"priority" = non-sheddable inference; "flex" = the cheaper best-effort lane |
GenerateContentConfig.service_tier="priority" / "flex" |
usageMetadata.serviceTier in the response body, then the x-gemini-service-tier header |
|
| Anthropic | Nothing — parameter ignored | — | — |
| Fireworks | Nothing — parameter ignored | — | — |
OpenAI sends fast; the echo is priority on GPT-5.6 rows and fast on GPT-6 rows
OpenAI renamed priority processing to Fast mode. The value sent on the request is "fast"
(legacy "priority" is identical). The value the response echoes back depends on the model
generation: the GPT-5.6 rows echo "priority", and the GPT-6 rows (gpt-6-astra,
gpt-6.1-sol, gpt-6-sol, gpt-6-luna) echo "fast", whichever of the two spellings was
sent (probed on all four rows and on gpt-5.6-luna, 2026-10-02; OpenAI's API reference still
says the echo is always priority). Never match on one
spelling when reading a response — the toolkit accepts either the tier name or the wire value
(openai.OPENAI_TIER_NAME_BY_WIRE_VALUE maps "fast" back to "priority"), bills both at the
priority row, and treats "default", "standard", or an empty value as a downgrade. Only a
value that is none of those warns once and bills standard with tier_downgraded set. "flex"
is sent and echoed verbatim on both providers, and so is "ultrafast" on OpenAI. On results the
tier is always reported under its toolkit name ("priority", never "fast").
flex is priced only where the vendor publishes a per-model price
"flex" is sent, read back and billed like any other tier, and it is gated on the registry
exactly as "priority" is: a model is offered the tier only if its row publishes a
service_tiers.flex price with a source. Five rows do today — gemini-3.8-flash
(0.375 / 0.0375 / 1.875, half of standard, the rate Google publishes for Flex and Batch) and the
four GPT-6 rows, gpt-6-astra (5 / 0.5 / 25 below 272K input tokens and 10 / 1 / 37.5
above), gpt-6.1-sol (1 / 0.05 / 5 and 2 / 0.1 / 7.5), gpt-6-sol (1 / 0.1 / 5 and
2 / 0.2 / 7.5) and gpt-6-luna (0.05 / 0.005 / 0.25
and 0.1 / 0.01 / 0.375), the model cards' "50% of Standard". On every other row
service_tier="flex" is dropped with a warning
and the request runs at standard tier — the same fail-cheap path any unpriceable tier takes;
the toolkit does not derive a price from a vendor's general rule.
Flex is the cheaper lane, not the dearer one, so a flex request served at standard costs
more than you asked for, not less. tier_downgraded is the same signal in both
directions — "you were not served the tier you asked for" — and requested_tier /
served_tier on each request record give the direction. On Google every request streams and
the served tier cannot be read (see "When the served tier cannot be read" below), so a flex
request there is normally recorded as served_tier="unverifiable" and billed at
standard — for flex that may over-state the cost, for priority under-state it; the marker
is what makes the two cases distinguishable.
The tier vocabulary is data-driven¶
There is no hardcoded list of valid tiers. A tier is valid for a model exactly when
model_information.json has a matching row under that model's service_tiers object:
"gpt-5.6-terra": {
"pricing_schema": "tiered_272k",
"input_less_than_272k_prompt": 2.0,
"output_less_than_272k_prompt": 12.0,
"input_greater_than_272k_prompt": 4.0,
"output_greater_than_272k_prompt": 18.0,
"cached_input": 0.2,
"service_tiers": {
"priority": {
"input_less_than_272k_prompt": 4.0,
"output_less_than_272k_prompt": 24.0,
"input_greater_than_272k_prompt": 8.0,
"output_greater_than_272k_prompt": 36.0,
"cached_input": 0.4
}
}
}
Request a tier a model has no row for and the provider layer drops it rather than sending an unpriceable request, logging:
DROPPING service_tier='priority' for <model>: no matching service_tiers pricing row in
model_information.json. Request proceeds at standard tier.
The run then proceeds normally at standard tier. Adding tier support for a new model is a registry edit, not a code change.
A provider offering a tier does not mean every one of its models is sold at it
claude-fable-5-1, claude-opus-5-5, claude-sonnet-5-5 and claude-haiku-5-5 are the clearest cases. No Anthropic row carries a
service_tiers object at all today, and Fable 5.1, Opus 5.5, Sonnet 5.5 and Haiku 5.5 are additionally not offered
on Priority Tier — so asking for one bills standard and logs
No 'priority' service-tier pricing for 'claude-fable-5-1' (or the other row's name). Each
registry row records that in a note, so a future Anthropic priority extension does not quietly
add one. The
vocabulary is data-driven — a model's allowed tiers are exactly the keys of its service_tiers
object — which makes this a registry fact rather than a hardcoded exception.
Models with a priority row¶
Every priced key in these tier rows is a flat multiple of the model's standard price, with one
published exception: gemini-3.5-flash-lite prices priority cached input at 0.05 — 1.67x its 0.03
standard cached rate, not 1.8x. That is Google's published figure (checked 2026-09-21), not a typo
for 0.054, and a _note on the pricing row says so; the 1.8x in the table below covers every other
priced key on that row.
| Provider | Models | Multiplier |
|---|---|---|
| OpenAI | gpt-6-astra, gpt-6.1-sol, gpt-6-sol, gpt-6-luna, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna |
2.0x (the pricing page's "Fast mode" table — priority under its new name) |
gemini-3.8-flash, gemini-3.7-flash, gemini-3.6-flash, gemini-3.5-flash, gemini-3.5-flash-lite, gemini-3.1-flash-lite, gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite |
1.8x |
Models with a flex row¶
| Provider | Model | Multiplier |
|---|---|---|
gemini-3.8-flash |
0.5x | |
| OpenAI | gpt-6-astra, gpt-6.1-sol, gpt-6-sol, gpt-6-luna |
0.5x, applied to every key of the tiered row |
gemini-3.8-flash and the four GPT-6 rows publish both tiers, so on those rows the same request
can be priced three ways (four on gpt-6-astra, which adds Ultrafast). OpenAI's "Fast mode" is the priority tier under its new name: the toolkit
sends service_tier="priority" on the wire as "fast" and recognises either echo as served.
Ultrafast (OpenAI, gpt-6-astra)¶
agent = Agent(model="gpt-6-astra", service_tier="ultrafast")
Ultrafast mode generates output several times faster than Fast mode, at six times the Standard price on every key. One row offers it today:
gpt-6-astra, per 1M tokens |
Input | Cached input | Cache write | Output |
|---|---|---|---|---|
| up to 272,000 input tokens | $60 | $6 | $75 | $300 |
| above 272,000 input tokens (whole request) | $120 | $12 | $150 | $450 |
- One model.
gpt-5.6-solis in a limited preview with no published price, so it has no row, and the API rejects the tier ongpt-6-sol,gpt-6.1-sol,gpt-6-lunaandgpt-5.6-luna(400 "Invalid service_tier argument", probed 2026-10-02). On any row without the tier the request is dropped with the warning above and runs at Standard; it is never sent. - A separate rate limit. Ultrafast requests draw from their own limit (1,000,000 tokens a minute at usage Tier 4), not the model's ordinary one.
- US or global processing only. It is not available with EU data residency.
- Billed by what was served. If OpenAI serves the request at Fast or Standard instead, the
request is billed at that tier's row and
tier_downgradedis set; an unreadable echo bills Standard. A six-times price is never charged on a guess.
What it buys, measured through Agent on 2026-10-02 (three streamed runs a tier, one reply of
about 260 output tokens):
| Tier | Time to first text | Total time | Output tokens a second | Cost of the reply |
|---|---|---|---|---|
| Standard | 1.5 to 2.5 s | 7.3 to 8.0 s | 46 to 49 | $0.016 |
Fast ("priority") |
1.3 to 2.1 s | 5.1 to 7.1 s | 46 to 69 | $0.031 |
| Ultrafast | 1.2 to 2.0 s | 2.3 to 3.5 s | 139 to 242 | $0.093 |
The gain is in generation speed, so it shows on long outputs. Time to first text does not
improve over Fast, and a run made of many short requests gains little over HTTP: a tool loop of
nine short requests, measured with the OpenAI SDK directly, took about 14.5 s on Ultrafast and on
Fast alike, because the time went on the round trips, not on generating text. The
WebSocket transport takes most of that overhead out: the same loop
took about 10 s over a socket, and through Agent 11.8 s against 17.3 s over HTTP. Use the two
together for an agent that calls tools.
model_information.json is the source of truth — check it rather than these tables when pricing
matters.
How tier pricing is applied¶
calculate_cost takes a service_tier argument and overlays the tier row onto the base price row:
if service_tier:
_tier_row = (model_price_info.get("service_tiers") or {}).get(service_tier)
if isinstance(_tier_row, dict):
model_price_info = {**model_price_info, **_tier_row}
Two consequences worth knowing:
- The overlay is a shallow merge, so any key the tier row does not define — an unpublished cached rate, for example — falls back to the base row rather than to zero.
- Tiered prompt-size pricing still applies on top. The 200k (Google) and 272k (OpenAI) thresholds are keys within the tier row, so a priority request with a 300k prompt prices at the priority above-threshold rate.
Calling calculate_cost with a tier the model does not define logs a warning and bills at standard
rates rather than failing.
Tier boundaries are inclusive on the low side¶
A prompt of exactly 200,000 tokens bills at a Google row's *_less_than_200k_prompt rates, and a
prompt of exactly 272,000 tokens at an OpenAI row's *_less_than_272k_prompt rates. This follows the
vendors' own wording: OpenAI's model cards price only prompts ">272K input tokens" at 2x input and
1.5x output for the whole request, and Google's pricing page splits on "prompts <= 200k tokens"
versus "prompts > 200k tokens".
The thresholds live in doc_processing_toolkit.core.TIERED_PRICING_THRESHOLDS, and the
estimate_task_cost MCP tool imports them, so an estimate cannot disagree with a real cost record.
Before this release both dispatch sites compared with a strict <, so a prompt landing exactly on
the boundary was priced at the long-context rates — roughly doubling that request, and flipping the
tier-specific cached rate early with it. It affected every tiered row and their priority overlays
(gpt-5.6-sol at 272,000 in / 1,000 out: $2.206000, now $1.108000).
Billing follows the served tier¶
Tier resolution happens per request, not per run, and each entry in
request_cost_records carries both sides:
| Field | Meaning |
|---|---|
requested_tier |
What you asked for ("priority", "flex", "ultrafast", or None) |
served_tier |
What actually ran ("priority", "flex", "ultrafast", "standard", or "unverifiable"; None when no tier was requested) |
served_tier_source |
Google only: how the served tier was established — "body", "header", or "none". A record from any other provider — a structured-output run's synthesised record included — omits the key rather than carrying None |
A request is priced at the row of the tier the provider reports having served. Usually that
is the tier you asked for. If the provider names a different tier it recognises (an "ultrafast"
request answered at "fast", say), the request is billed at that tier's row and marked
tier_downgraded. Anything else — an explicit "default", an unrecognized value, or an unreadable
signal — resolves to standard and is billed at standard rates. This is deliberately fail-cheap:
an ambiguous signal never bills you at a premium.
When the served tier cannot be read¶
Both of the toolkit's Google entry points stream (streamGenerateContent?alt=sse), and that response
carries no x-gemini-service-tier header — the header exists only on the non-streaming
generateContent. The served tier is in the response body as usageMetadata.serviceTier, but
google-genai 2.24.0 and 2.25.0 do not declare the field and its converter copies known keys only, so the
SDK drops it. The toolkit reads the body first anyway: the moment a google-genai release declares the
field, billing starts following the served tier with no change here, because the dependency floor is
uncapped.
Until then a streamed Google priority request records served_tier: "unverifiable" and
served_tier_source: "none", is billed at standard rates, and sets tier_downgraded. One
warning per process says why. Read the marker carefully:
"standard"means Google told us it downgraded, so the recorded cost is right."unverifiable"means the toolkit could not tell. Google may still have served — and invoiced — the premium, so the recorded cost may be under-stated. Only the provider invoice settles it.
Before this release the same situation was recorded as a plain downgrade, which was both a systematic under-bill and a false marker on every streamed Google priority request.
A request that fails in flight is the same case on OpenAI: the served tier arrives with the event
that ends the response, so a request that never finishes has none. Its cost is an estimate at
standard rates, and requests that completed before the failure keep the tier they were served
at. The marker is not uniform on failed runs. A tool-loop run that a failure event ends after part of
a reply was streamed reports tier_downgraded: True. A run that exhausts its retries returns the
short failure result, which carries no tier fields on any provider, so tier_downgraded reads
False there although the estimates are at standard rates. On a tier that costs a multiple of
standard (Ultrafast is six times), check a failed run against the invoice.
The run-level answer¶
Tier resolution is per request — a long tool-calling run can have one request served at the tier it
asked for and the next silently downgraded — so AgentResult collapses the records to one answer:
| Field | Meaning |
|---|---|
service_tier_served |
"priority", "flex", "ultrafast", "standard", "unverifiable", "mixed" when the requests disagree, or None when the run asked for no tier at all |
priority_request_count |
How many requests actually billed at the model's priority row. Requests served at "ultrafast" are not counted here; read served_tier on the records for those |
None is deliberately not "standard": "never asked" and "asked and was refused" are different
facts to anyone reconciling an invoice. Both fields are derived from request_cost_records — on
AgentResult and on DetailedCostReport alike — so a saved cost report can never disagree with its
own records, and agent.cost.summarize_served_tiers(records) is the supported way to compute the
pair yourself.
Tier handling covers every run shape, including runs that take the structured-output path (an
output_schema with no local tools). Before 0.3.0 the parameter was wired into the tool-calling
loops only, so a no-tools run silently ran — and billed — at standard tier no matter what was asked
for.
The Google truncation retry keeps the tier
A MAX_TOKENS-truncated response on Gemini's no-tools path is retried once with a higher
ceiling by calling the same function again, and that call did not forward service_tier: a
priority (or flex) request that truncated was re-issued at standard and its record came back
with requested_tier=None. The truncated attempt itself — a complete request the provider
billed — was missing from the run entirely, because the retry reports only its own usage. The
tier is now forwarded, and the truncated attempt is folded in the way a failed-and-retried
attempt already is: the token and cost totals include it and retry_details names it.
Each downgrade logs a warning naming the evidence, for example:
OpenAI served this request at standard tier despite the priority request
(response service_tier='default') — billed at standard rates.
Detecting downgrades in code¶
AgentResult.tier_downgraded is True when any request in the run was not served at the tier it
asked for:
result = await agent.run("Summarize this filing")
if result.tier_downgraded:
log.warning("A tier was requested but at least one request did not get it")
It defaults to False, and stays False when no tier was requested at all — so it flags a real
missed expectation rather than merely "this run was standard tier".
The flag is the same signal in both directions, but it does not tell you which way the money moved.
For "priority" it is the silent downgrade the flag was named for. For "flex" it points the other
way: flex is the cheaper row, so being served "standard" bills more than asked. Read
requested_tier / served_tier on request_cost_records, or service_tier_served on the result,
for the direction.
Configuration¶
agent = Agent(model="gemini-3.6-flash", service_tier="priority")
agent = (
AgentBuilder()
.model("gemini-3.6-flash")
.service_tier("priority")
.build()
)
model: gemini-3.6-flash
service_tier: priority
When to use it¶
Priority tiers buy latency and scheduling predictability, not quality — the model and its output are
identical. Reach for one when a run is user-facing and latency-sensitive, or when shedding under load
is unacceptable. Skip it for batch and background work, where the 1.8x-2x premium buys nothing you
are waiting on. Ultrafast is the same trade at a steeper price: use it where a person is waiting on
a long answer from gpt-6-astra, and not for runs dominated by tool calls or short replies.
Because billing follows the served tier, requesting priority on a model without a registry row costs nothing extra; it simply runs standard.
Related pages¶
- Cost Tracking Report recipe — request priority or flex and read the served tier off a real run
- Cost Tracking — per-request cost records and how token costs are assembled
- Providers — provider-specific capabilities and parameter support
- Prompt Caching — the other lever on per-token cost
- Agent — the
Agentclass and its parameters