Skip to content

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")
Google "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)
Google 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
Google 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-sol is in a limited preview with no published price, so it has no row, and the API rejects the tier on gpt-6-sol, gpt-6.1-sol, gpt-6-luna and gpt-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_downgraded is 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.

  • 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 Agent class and its parameters