◉GEMINI LABJP
●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — The old Deep Research agent shut down on 10/23. If calls fail, move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●CLI — Gemini API CLI (gemini-api) is out in beta (10/27): a zero-dependency client for agents and CI●STORE — New paid projects now save Generate Content requests by default (store on) as of 10/26●NEW — Stop Re-Reading Every Crash Report: Gre…●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — The old Deep Research agent shut down on 10/23. If calls fail, move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●CLI — Gemini API CLI (gemini-api) is out in beta (10/27): a zero-dependency client for agents and CI●STORE — New paid projects now save Generate Content requests by default (store on) as of 10/26●NEW — Stop Re-Reading Every Crash Report: Gre…
Articles/API / SDK
◈ API / SDK/2026-07-29Advanced

A 17% Drop in Output Tokens Sounded Big. Then I Decomposed the Bill

Output pricing fell 16.67% and output tokens fell about 17%. Adding those numbers does not give you the savings. Here is an exact price/volume/cross-term decomposition, plus a loop measurement where the savings rate went down instead of up.

Gemini API252Cost EngineeringToken AccountingAgents8Operations12

✦ Premium Article

When Gemini 3.6 Flash quietly replaced the Flash model I was calling in the week of July 21, the first thing I did was arithmetic in my head.

Output pricing moving from $9.00 to $7.50 per million tokens. Call it 16.67% off. On top of that, reports of roughly 17% fewer output tokens for the same work.

So, about 33% cheaper.

I rewrote a budget sheet for my automation pipelines on that assumption, and the numbers refused to line up. Some pipelines showed a tenth of the savings I had penciled in.

Two mistakes. I had added two factors that needed to be multiplied. And I had ignored the fact that input pricing did not move at all.

What follows is the decomposition I wrote to clear both up, and the output of actually running it. Prices are the ones published as of July 2026 — they change, so verify against primary sources before you make a call.

When price and volume move together, the order of subtraction matters

Output cost is unit price times quantity. Price goes $9.00 to $7.50, quantity goes 1.00 to 0.83, and the cost lands at 7.50 × 0.83 = 6.225.

That is a 30.83% reduction, not the 33.67% you get by adding.

The missing 2.84 points is the overlap: the discounted price is being applied to the tokens that no longer exist. Double-counted savings.

Economists call this the cross term, and it shows up any time you attribute a change in a product of two factors. It is not small enough to wave away.

from dataclasses import dataclass
 
@dataclass(frozen=True)
class Price:
    name: str
    inp: float   # USD / 1M input tokens
    out: float   # USD / 1M output tokens
 
OLD = Price("gemini-3.5-flash", 1.50, 9.00)
NEW = Price("gemini-3.6-flash", 1.50, 7.50)
 
def cost(p: Price, ti: int, to: int) -> float:
    return (ti / 1_000_000) * p.inp + (to / 1_000_000) * p.out
 
def decompose(old_p: Price, new_p: Price, to_old: int, to_new: int):
    """Split the output-side delta into price / volume / cross effects.
    The three must sum to the observed delta, up to floating point noise."""
    q0, q1 = to_old / 1_000_000, to_new / 1_000_000
    p0, p1 = old_p.out, new_p.out
    price_effect = (p1 - p0) * q0          # move price only
    volume_effect = (q1 - q0) * p0         # move quantity only
    cross = (p1 - p0) * (q1 - q0)          # the part that moved together
    total = p1 * q1 - p0 * q0
    residual = abs((price_effect + volume_effect + cross) - total)
    if residual > 1e-9:
        raise AssertionError(f"decomposition does not close: residual {residual}")
    return price_effect, volume_effect, cross, total

The residual assertion is there because attribution formulas break silently. Adjust one term later and the identity stops holding without any visible symptom.

Three workloads through the same decomposition

I collapsed my own traffic into three shapes — input-heavy, output-heavy, and balanced — and ran them. These are program outputs, not invoice figures.

WorkloadInput tokensOutput tokensOutput share of old billTotal savings
A Classification batch (input-heavy)2,000,00040,00010.7%3.30%
B Draft generation (output-heavy)30,000120,00096.0%29.60%
C Conversation (balanced)200,00060,00064.3%19.82%

Same model, same 17% reduction, and the bill moves anywhere from 3.30% to 29.60%.

Workload A breaks down as $3.3600 before, $3.2490 after. The −$0.11100 delta is a −$0.06000 price effect, a −$0.06120 volume effect, and a +$0.01020 cross term.

Note the sign on that cross term. When price and quantity fall together, the cross term pushes back against the savings. That is precisely why the additive estimate always errs on the optimistic side.

And A stalls for a structural reason: output is only 10.7% of the bill. The other 89.3% is input at an unchanged $1.50. Most of the spend sits where the discount never lands.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦Adding 16.67% and 17% gives 33.67%; composing them correctly gives 30.83% — the 2.84-point gap traced to the cross term with runnable code
✦The same 17% reduction produces 3.30% savings on an input-heavy classification batch and 29.60% on output-heavy drafting
✦A 12-turn agent loop where savings fell from 20.40% to 15.59%, and history pruning that beat the model switch at 26.9%
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◈ API / SDK2026-09-02
Taking stock of Gemini standard API keys before September, across CI, servers, and my own machine
Standard API keys, including restricted ones, stop being accepted during September. Because this changes where keys come from rather than what they say, here is the three-layer inventory I ran across code, deploy settings, and my local machine, plus a dual-path client to switch behind.
◈ API / SDK2026-10-27
New paid projects now start with request storage switched on. Three places to check the day you notice
Gemini API paid projects created on or after October 26, 2026 store generateContent requests by default. How to tell whether your project is affected, how to turn storage off in Logs Settings, how to set store=False per request, and how to shorten retention, following the official wording.
◈ API / SDK2026-10-14
When Your config Raises "Extra inputs are not permitted", Check the Spelling of Every Key
If a google-genai call fails with a pydantic ValidationError (extra_forbidden) the moment you pass a config, it is almost always a one-letter typo. Here is the output I reproduced, a small function that checks keys before the call, and how to tell why response.text came back as None.