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: 14count_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 who has shipped apps since 2014—where AdMob revenue minus server cost is the whole margin—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 kept3. 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.
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.
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.