◉GEMINI LABJP
●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 6 days left. 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●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●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 — deep-research-pro-preview-12-2025 shuts down 10/23, 6 days left. 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●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…
Articles/API / SDK
◈ API / SDK/2026-03-14Intermediate

Estimating Gemini API Costs Before You Send — count_tokens in Practice

Use Gemini's free count_tokens call to measure input tokens and costs before each request, then cut spend with caching and model selection.

Gemini API249TokensCost Management5Optimization

Count Tokens Before You Get Billed

Gemini API charges by the tokens you exchange. The tricky part is that a long context block or a single high-resolution image can balloon your token count well past what you expected. I run a small feature that classifies app-store reviews with Gemini, and the first prototype quietly attached the full product description and a boilerplate template to every review—tripling the input tokens per request before I noticed.

Fortunately, Gemini ships a count_tokens call that measures input size before you send, and the call itself is free. You can check exactly how many tokens a prompt will consume before committing to a billable request. Making that a habit alone keeps your cost estimates from drifting wildly.

from google import genai
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
response = client.models.count_tokens(
    model="gemini-2.5-flash",
    contents="Classify the sentiment of this app review into five levels.",
)
 
print(f"Input tokens: {response.total_tokens}")
# Input tokens: 14

count_tokens reports input tokens. Output tokens aren't known until generation happens, but if you cap them with max_output_tokens, you can bound the worst case on the output side too.

Building a Feel for the Unit

A token is the smallest unit a model processes. One token is roughly four characters of English, but it shifts a lot with language and symbols. For quick daily math, these rules of thumb help:

English prose : ~1.3 tokens per word
Japanese text : ~1 token per character
Source code   : ~10-20 tokens per line
Images        : a few hundred to ~2,000 tokens by resolution

With that intuition you can mentally ballpark a prompt and immediately spot when count_tokens returns something an order of magnitude off—a sign you're sending something extra. In my case, the culprit was pasting the same multi-thousand-character classification rules into every user message. Caching, covered below, fixes exactly that.

Counting Text and Image Tokens

Gemini handles images and video alongside text, and in multimodal requests the image often dominates the input tokens. Measure those a notch more carefully than text-only prompts. You can mix an image straight into contents with types.Part.from_bytes.

import pathlib
from google import genai
from google.genai import types
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
image_bytes = pathlib.Path("screenshot.png").read_bytes()
 
response = client.models.count_tokens(
    model="gemini-2.5-flash",
    contents=[
        types.Part.from_bytes(data=image_bytes, mime_type="image/png"),
        "List the three main elements in this screenshot.",
    ],
)
 
print(f"Text + image input tokens: {response.total_tokens}")

Image tokens scale with resolution. For tasks like classification or summarization where fine detail isn't needed, resizing the long edge down to around 1,024px before sending cut my measured input tokens by more than half for the same subject.

A Calculator for Monthly Cost

Once you can read token counts, multiply them against the rate card to estimate monthly spend. Prices change, so always confirm the latest figures on the official pricing page and keep them in one place in your code. The example below uses approximate rates (per 1M tokens, USD) as of June 2026.

class GeminiCostCalculator:
    """Rough Gemini API cost estimates. Verify the rates."""
 
    # Per 1M tokens (USD) — approximate, as of June 2026
    PRICING = {
        "gemini-2.5-flash": {"input": 0.30, "output": 2.50},
        "gemini-2.5-pro":   {"input": 1.25, "output": 10.00},  # tiers up above 200k context
    }
 
    @classmethod
    def request_cost(cls, input_tokens, output_tokens, model="gemini-2.5-flash"):
        if model not in cls.PRICING:
            raise ValueError(f"Unknown model: {model}")
        p = cls.PRICING[model]
        input_cost = input_tokens / 1_000_000 * p["input"]
        output_cost = output_tokens / 1_000_000 * p["output"]
        return round(input_cost + output_cost, 6)
 
    @classmethod
    def monthly_cost(cls, daily_requests, avg_in, avg_out, model="gemini-2.5-flash"):
        per_day = cls.request_cost(avg_in, avg_out, model) * daily_requests
        return round(per_day * 30, 2)
 
 
# 1,000 requests/day, avg 500 in / 300 out tokens, on Flash
monthly = GeminiCostCalculator.monthly_cost(1000, 500, 300, "gemini-2.5-flash")
print(f"Estimated monthly cost: ${monthly}")

Model selection is where the real savings sit. Estimate the same workload on Pro versus Flash and you'll see input rates differ by roughly 4x, with output diverging further. Simple-judgment tasks like review classification run fine on Flash. As an indie developer, I live with the fact that AdMob revenue minus server cost is what actually stays in my pocket, so I treat variable costs like API spend as something to make predictable, and consolidating onto Flash alone roughly halved my monthly bill. Route only complex reasoning or long-form generation to Pro.

Three Practical Ways to Cut Cost

1. Move repeated context into a cache

Pasting the same instructions or a long reference document into every prompt means paying for those input tokens every time. Gemini's context caching lets you read the reused portion at a discounted rate. The 2.5 models apply implicit caching automatically, but for fixed text you want to guarantee, registering it explicitly is the dependable route.

from google import genai
from google.genai import types
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
cache = client.caches.create(
    model="gemini-2.5-flash",
    config=types.CreateCachedContentConfig(
        system_instruction="You classify app reviews into five sentiment levels.",
        contents=["(put the long rule set and labeled examples here)"],
        ttl="3600s",
    ),
)
 
response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="One star: crashes every time I open it. Awful.",
    config=types.GenerateContentConfig(cached_content=cache.name),
)
 
usage = response.usage_metadata
print(f"Regular input: {usage.prompt_token_count}")
print(f"Cached read:   {usage.cached_content_token_count}")

For my review classifier, moving the multi-thousand-character rule set into a cache visibly dropped the real input tokens per request.

2. Prune the context you pass

Stacking the full conversation history makes input tokens snowball over many turns. Dropping the oldest messages to stay within a budget is a simple, effective fix.

def prune_history(messages, max_tokens=4000):
    """Keep the newest messages within a token budget."""
    kept, total = [], 0
    for msg in reversed(messages):
        approx = len(msg["content"].split()) * 1.3  # rough estimate
        if total + approx > max_tokens:
            break
        kept.insert(0, msg)
        total += approx
    return kept

3. Cap output tokens

It's easy to fixate on input, but output is the pricier side. Setting max_output_tokens to fit the task prevents a runaway long response from producing a surprise bill.

Capping too tightly has its own trap, though. On the 2.5 models, tokens spent on internal thinking count toward the output side. Because my classification replies are short, I once set the cap absurdly low and then spent half a day wondering why the response text came back empty. I only noticed the thinking tokens had eaten the whole budget after laying the usage_metadata fields side by side. These days, before shipping, I measure real output and thinking token counts and set the cap to their maximum plus some headroom.

Streaming to Avoid Wasted Retries

Waiting on a long response invites timeouts, and every resend pays the input tokens again. Streaming delivers partial results as they generate, which improves perceived speed and cuts wasted spend from duplicate requests. The final chunk carries usage_metadata, so you can log actual consumption directly.

from google import genai
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
stream = client.models.generate_content_stream(
    model="gemini-2.5-flash",
    contents="Summarize these reviews and list the top three feature requests.",
)
 
usage = None
for chunk in stream:
    if chunk.text:
        print(chunk.text, end="", flush=True)
    if chunk.usage_metadata:
        usage = chunk.usage_metadata
 
if usage:
    print(f"\nInput {usage.prompt_token_count} / Output {usage.candidates_token_count}")

Since the count_tokens call is free, run it once before each production request and log the input estimate—it makes tracking down cost anomalies far easier later.

Log the Gap Between Estimate and Actual

count_tokens before sending is only a forecast of the input side. What you're billed on is the usage_metadata that comes back with the response. If you keep both on the same row, your own estimating habits become visible within a few days.

import json
import time
 
def call_with_ledger(client, model, contents, config=None, log_path="token_ledger.jsonl"):
    estimated = client.models.count_tokens(model=model, contents=contents).total_tokens
    res = client.models.generate_content(model=model, contents=contents, config=config)
    u = res.usage_metadata
    row = {
        "ts": int(time.time()),
        "model": model,
        "estimated_in": estimated,
        "billed_in": u.prompt_token_count,
        "cached_in": u.cached_content_token_count or 0,
        "thoughts": getattr(u, "thoughts_token_count", 0) or 0,
        "out": u.candidates_token_count,
    }
    with open(log_path, "a", encoding="utf-8") as f:
        f.write(json.dumps(row, ensure_ascii=False) + "\n")
    return res

When the gap is large, I suspect something that wasn't part of the estimate, such as a system instruction or a cache. Thinking tokens land in their own thoughts column, so the log also helps you pick an output cap. Calling count_tokens every time adds a round trip, so sampling a fraction of requests may be enough.

Estimate before you send, record the actual right after it returns, and keep both on one row. I don't start talking about cost until that row exists.

Your Next Step

Pick the single operation you run most often and measure its real input tokens with count_tokens. More often than not, you'll immediately find a fixed block you paste every time or an oversized image resolution you can trim. When you want more responsiveness too, pair this with the streaming and chat patterns and the multimodal API guide for handling images and video.

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 →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

◈ 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.
◈ API / SDK2026-10-08
When a Deprecation Has No Shutdown Date: Re-Picking an Image Model on Three Conditions
gemini-nano-banana-2.1 went GA on October 6, and gemini-3.1-flash-image was deprecated with no shutdown date. Here is a table and a short script for choosing a replacement by resolution, reference images, and search grounding.
◈ API / SDK2026-09-29
The Size Column Said 'F6' and 'About A4' — I Let Gemini Read It, and Let a Table Do the Math
An artist's spreadsheet mixed 'F6', '41×31.8cm', and 'about A4' in one size column. Here is the small tool I built: Gemini reads each cell, a lookup table converts to centimeters, and the artwork image decides the orientation. Working code, step by step.