GEMINI LABJP
NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Articles/API / SDK
API / SDK/2026-04-19Advanced

Gemini API Caching in Production — Operational Notes from an Indie Mobile Developer

Field notes on running Gemini API's Context Caching and Implicit Caching together inside indie mobile apps. Includes working Python code, six months of measured costs from AdMob-funded apps, and seven non-obvious operational pitfalls.

gemini-api277caching2cost-optimization30python104production140

Premium Article

I'm Masaki Hirokawa (@dolice), an artist and indie developer based in Japan. Since 2014 I've built and operated a family of iPhone and Android apps — wallpapers, calming visuals, manifestation tools — with around 50 million cumulative downloads, monetised mostly through AdMob. That last detail is important for this article: the monthly Gemini API bill comes straight out of ad revenue, so it isn't an abstract optimisation problem for me. It's the same line item as my electricity bill.

The trigger for taking caching seriously was a small one. I added a feature to one of the wallpaper apps that generates a short poem matching the mood of each image. The first month's Cloud Billing email said $412 — about three times what I'd expected — and the cause was almost entirely the system prompt and brand guidelines being shipped on every request. AdMob revenue was still in the black, but I sat with that bill for a while before opening the docs on Context Caching and Implicit Caching.

What follows is what six months of operating these two caching mechanisms in production taught me. Every code sample has been verified in a working system. Numbers come from real Cloud Billing and AdMob reports.

Why Caching Matters: The Gemini API Cost Structure

Before diving into implementation, it helps to understand exactly where the money goes.

Gemini API billing splits into three buckets: input tokens, output tokens, and cache storage. The first one is the killer: input tokens are billed on every single request.

Consider a customer support bot with a 10,000-token system prompt that handles 1,000 requests per day. That's 10 million tokens per day, 300 million per month. At gemini-2.5-pro pricing (~$1.25 per million input tokens up to 200K), the system prompt alone costs about $375 a month — and the user hasn't received a single useful answer yet.

Caching changes the equation:

  • Context Caching: Explicitly cache a chunk of content and reference it by ID in subsequent requests. Cached tokens are billed at approximately a 75% discount versus full price.
  • Implicit Caching: When the same prompt prefix repeats, discounted pricing kicks in automatically (gemini-2.0-flash and newer).

Picking the right one for each call site — and combining them deliberately — is what actually drives bills down.

What Caches Well, and What Doesn't

Not every payload benefits. The big wins come from "fixed context reused across requests":

  • Long system prompts (5,000+ tokens of spec, instructions, brand guidelines)
  • Reference documents (product manuals, API specs, legal text)
  • Image and video files used as multimodal context
  • Whole codebases used for code review or Q&A

User messages that change every call are not cacheable — and trying to design as if they were just wastes effort. Separating "the part that never changes" from "the part that does" is the first real step toward caching efficiency.

In my wallpaper app, the fixed context — world-building, tone rules, banned-word list, output format spec — adds up to about 12,400 tokens. The user message is just an image embedding and a couple of lines of instruction. That split alone took the monthly bill from $281 to $96 over six months.

For a broader treatment of cost optimisation, see Gemini API Cost Optimization Complete Guide.

How Context Caching Works (and How to Implement It)

Context Caching is an explicit API: create the cache, get back an ID, then reference that ID from later requests to get the discount.

Prerequisites — Check These First

There are a few conditions that aren't obvious until you've tripped on them:

  • Minimum token count: The content being cached must have a minimum number of tokens (2,048+ for gemini-2.5-pro).
  • Supported models: Not every model qualifies. gemini-2.0-flash, gemini-2.5-pro, and gemini-2.5-flash are the main ones currently supported.
  • Cache TTL: Default is one hour, extendable up to 24 hours.

The minimum-token requirement is easy to miss; cache something shorter and you'll get a BadRequestError.

Creating a Cache (Basic Implementation)

Here's a minimal pattern for caching a long system prompt.

import google.generativeai as genai
from google.generativeai import caching
import datetime
import time
 
# In real code, pull the key from an environment variable
genai.configure(api_key="YOUR_GEMINI_API_KEY")
 
SYSTEM_DOCUMENT = """
You are an advanced customer support AI. Follow the rules and knowledge base below.
 
[Product documentation: 10,000+ characters of detailed spec]
--- Product Spec ---
Product Name: ExampleApp Pro
Version: 3.2.1
... (long content continues) ...
"""  # Use real content that adds up to 2,048+ tokens
 
def create_cache() -> caching.CachedContent:
    """Create a Context Cache and return the cached content object."""
    try:
        cached_content = caching.CachedContent.create(
            model="models/gemini-2.5-pro",
            display_name="customer-support-system-prompt",  # Choose a searchable name
            system_instruction=SYSTEM_DOCUMENT,
            ttl=datetime.timedelta(hours=12),  # Retain cache for 12 hours
        )
        print(f"OK Cache created: {cached_content.name}")
        print(f"   Expires: {cached_content.expire_time}")
        print(f"   Tokens: {cached_content.usage_metadata.total_token_count}")
        return cached_content
    except Exception as e:
        print(f"FAIL Cache creation failed: {e}")
        raise
 
def chat_with_cache(cached_content: caching.CachedContent, user_message: str) -> str:
    """Send a chat request that references the cache."""
    try:
        model = genai.GenerativeModel.from_cached_content(cached_content)
        response = model.generate_content(user_message)
 
        # Verify the cache is actually being used
        usage = response.usage_metadata
        print(f"   Cached tokens: {usage.cached_content_token_count}")
        print(f"   Prompt tokens (total): {usage.prompt_token_count}")
        print(f"   Output tokens: {usage.candidates_token_count}")
 
        return response.text
    except Exception as e:
        print(f"FAIL Request failed: {e}")
        raise
 
if __name__ == "__main__":
    cache = create_cache()
 
    questions = [
        "What is your return policy?",
        "How long is the product warranty?",
        "What are your support hours?",
    ]
 
    for question in questions:
        print(f"\nQ: {question}")
        answer = chat_with_cache(cache, question)
        print(f"A: {answer[:100]}...")
        time.sleep(1)

The line worth memorising is response.usage_metadata.cached_content_token_count. If this is zero, the cache wasn't actually used and something's wrong — monitor this value in production.

Cache Lifecycle Management

In production you need to handle list/update/delete cleanly. This manager class is what I run in my apps.

import google.generativeai as genai
from google.generativeai import caching
import datetime
from typing import Optional
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
 
class CacheManager:
    """CRUD operations for Context Cache."""
 
    @staticmethod
    def list_caches() -> list:
        """List currently active caches."""
        caches = list(caching.CachedContent.list())
        print(f"Active caches: {len(caches)}")
        for c in caches:
            now = datetime.datetime.now(datetime.timezone.utc)
            remaining = (c.expire_time - now).total_seconds()
            print(f"  - {c.display_name}: {remaining/3600:.1f}h remaining")
        return caches
 
    @staticmethod
    def get_cache_by_name(display_name: str) -> Optional[caching.CachedContent]:
        """Look up a cache by name (important for reuse across process restarts)."""
        for c in caching.CachedContent.list():
            if c.display_name == display_name:
                now = datetime.datetime.now(datetime.timezone.utc)
                remaining_hours = (c.expire_time - now).total_seconds() / 3600
                if remaining_hours > 0.5:  # Only use if 30+ minutes left
                    return c
        return None
 
    @staticmethod
    def extend_cache_ttl(cache: caching.CachedContent, hours: int = 24):
        """Extend a cache's TTL."""
        try:
            cache.update(ttl=datetime.timedelta(hours=hours))
            print(f"OK TTL extended: {cache.display_name} -> +{hours}h")
        except Exception as e:
            print(f"FAIL TTL update failed: {e}")
 
    @staticmethod
    def delete_cache(cache: caching.CachedContent):
        """Delete a cache to stop accruing storage costs."""
        try:
            display_name = cache.display_name
            cache.delete()
            print(f"DELETED Cache: {display_name}")
        except Exception as e:
            print(f"FAIL Delete failed: {e}")

get_cache_by_name matters more than it looks. If a server restart creates a fresh cache every time, you pile up storage costs for orphaned caches. Look up the existing one by name and reuse it.

Worked Example: Context Caching Cost

Concrete numbers help.

Setup: gemini-2.5-pro, 10,000-token system prompt, 1,000 requests/day

Without caching (monthly):

  • Input tokens: 10,000 × 1,000 × 30 = 300M tokens
  • Estimated cost: $375 / month

With Context Caching (monthly):

  • Cache creation + storage (12h TTL, daily refresh): ~$24 / month
  • Cache reference (75% discount on cached portion): ~$95 / month
  • Total: ~$119 / month (about 68% savings)

These numbers assume the system prompt was previously sent on every request. Real prices vary — check the Google AI pricing page for the latest.

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
Six months of measured monthly cost on a wallpaper app (50M cumulative downloads): $281 → $98 with Context Caching, plus how the hit rate moved week by week
Seven implementation insights that aren't in the official docs (min-token requirements, hidden storage costs, display_name collisions, expiry races, count_tokens being billable)
Decision rules for combining Flash + Pro with caching in AdMob-funded apps, a TTL cheat sheet by task type, and a 'monthly budget ÷ 30 × 1.3' billing alert formula
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-07-02
Routing Between Local Gemma 4 and the Gemini API Cut My Bill from ¥32,000 to ¥9,000 — A Production Hybrid Router Design
How I cut a ¥32,000/month Gemini API bill to the ¥9,000 range with hybrid inference: routing design, a full Python router, production pitfalls, and how Gemma 4 arriving on the Gemini API in July 2026 changes the decision.
API / SDK2026-05-05
Cutting Gemini API Costs by 80%: Context Caching and Implicit Caching
A hands-on guide to reducing Gemini API costs by 80% using Context Caching and Implicit Caching. Includes decision frameworks, working code examples, and a troubleshooting checklist for when caching stops working in production.
API / SDK2026-04-07
Gemini API Semantic Router: Implementation Notes for Splitting Flash and Pro Smartly
Implementation notes for building a production-grade semantic router that automatically dispatches Gemini queries between Flash and Pro. Includes Python and TypeScript working code, a two-stage design pattern, and seven implementation insights from running it inside an indie wallpaper app.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →