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-05-05Intermediate

Fixing Gemini API Rate Limit Errors: A Complete Troubleshooting Guide

How to handle Gemini API 429 Too Many Requests and RESOURCE_EXHAUSTED errors. Covers exponential backoff, batch processing strategies, and practical patterns for staying within rate limits.

Gemini API192rate limit2429 errorRESOURCE_EXHAUSTEDtroubleshooting82

The first scaling challenge most Gemini API users hit isn't a logic bug — it's a 429 Too Many Requests error.

Rate limit errors are a signal, not a failure. They mean your application is outpacing the allocated throughput. The fix isn't just "wait longer" — it's building retry logic that handles limits gracefully and pacing requests intelligently from the start.

Understanding Gemini API Rate Limits

The API enforces three types of limits:

  • RPM (Requests Per Minute): Maximum requests per minute
  • TPM (Tokens Per Minute): Maximum tokens processed per minute
  • RPD (Requests Per Day): Maximum requests in a 24-hour period

Free tier limits (Google AI Studio and the Gemini API free tier) are tight enough that you'll hit them during active development. Even paid plans can see limits under sudden traffic spikes.

Check Google's official rate limit documentation for current values — they vary by model and plan tier.

The Foundation: Exponential Backoff

When a rate limit error occurs, the right response is a retry with increasing wait times between attempts. This is exponential backoff, and it's the standard pattern for handling transient API errors.

import time
import random
import google.generativeai as genai
from google.api_core import exceptions
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel("gemini-2.5-flash-preview-05-20")
 
def generate_with_retry(prompt: str, max_retries: int = 5) -> str:
    """
    Generate content with automatic exponential backoff retry.
    """
    for attempt in range(max_retries):
        try:
            response = model.generate_content(prompt)
            return response.text
            
        except exceptions.ResourceExhausted as e:
            if attempt == max_retries - 1:
                raise  # Re-raise after final attempt
            
            # Exponential backoff: 2^attempt seconds + random jitter
            wait_time = (2 ** attempt) + random.uniform(0, 1)
            print(f"Rate limit hit. Retrying in {wait_time:.1f}s... (attempt {attempt + 1}/{max_retries})")
            time.sleep(wait_time)
            
        except Exception as e:
            raise  # Don't retry non-rate-limit errors
 
try:
    result = generate_with_retry("Explain Python async/await with an example.")
    print(result)
except exceptions.ResourceExhausted:
    print("Max retries reached. Wait a minute before trying again.")

The random.uniform(0, 1) jitter matters more than it looks. Without it, multiple clients retrying simultaneously will all fire at the same second — the thundering herd problem. Jitter spreads those retries out.

Batch Processing with Rate-Aware Pacing

For processing lists of prompts, pre-calculating request intervals prevents hitting the limit in the first place:

import time
import asyncio
import google.generativeai as genai
from google.api_core import exceptions
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel("gemini-2.5-flash-preview-05-20")
 
async def process_batch(prompts: list[str], rpm_limit: int = 10) -> list[str]:
    """
    Process a list of prompts while respecting RPM limits.
    rpm_limit: maximum requests per minute to stay safely under the limit
    """
    results = []
    delay = 60.0 / rpm_limit  # Minimum seconds between requests
    
    for i, prompt in enumerate(prompts):
        try:
            response = model.generate_content(prompt)
            results.append(response.text)
            
            if i < len(prompts) - 1:
                await asyncio.sleep(delay)
                
        except exceptions.ResourceExhausted:
            print("Rate limit hit during batch. Waiting 30s...")
            await asyncio.sleep(30)
            
            try:
                response = model.generate_content(prompt)
                results.append(response.text)
            except Exception as e:
                results.append(f"Error: {str(e)}")
    
    return results
 
async def main():
    prompts = [
        "Explain list comprehensions in Python",
        "How do Python dictionaries work?",
        "Show a basic Python class definition",
    ]
    
    results = await process_batch(prompts, rpm_limit=10)
    for i, result in enumerate(results):
        print(f"--- Result {i+1} ---")
        print(result[:200])
 
asyncio.run(main())

Common Mistakes and Their Fixes

Mistake 1: No error handling in loops

# ❌ One rate limit error kills the entire batch
for prompt in large_list:
    response = model.generate_content(prompt)  # Unhandled exception stops everything
    results.append(response.text)
# ✅ Handle the error and recover per-item
for prompt in large_list:
    try:
        response = model.generate_content(prompt)
        results.append(response.text)
    except exceptions.ResourceExhausted:
        time.sleep(60)
        response = model.generate_content(prompt)
        results.append(response.text)

Mistake 2: Sending large prompts without chunking

Token-heavy requests hit TPM limits faster. Long documents are better split into chunks and processed in separate requests, spreading TPM consumption across time.

Mistake 3: Not reading the error message

ResourceExhausted errors often include which specific limit was hit (RPM, TPM, or RPD). Reading the error message before designing your retry strategy saves time.

Development Tips for Free Tier Usage

When building on the free tier, a few habits reduce unnecessary limit hits:

Cache responses. During iterative development, save API responses locally and reuse them instead of re-fetching. This is especially useful when testing prompt variations.

Test with short prompts. Develop and test logic with minimal prompts, then validate with full-length prompts before shipping.

Separate dev and production API keys. Mixing development traffic with production makes limit management much harder to reason about.

For a broader look at the Gemini API, the Gemini API quickstart guide is a good starting point. For model selection context, the Gemini 2.5 Flash complete guide covers how Flash's characteristics affect throughput planning.

Count Tokens Before Sending to Avoid TPM Limits

TPM (tokens per minute) is the limit you can hit even with a low request count. Sending one long document can reach the ceiling on its own.

Counting tokens before you send heads this off. count_tokens makes an API call but doesn't consume generation quota or cost anything, so use it freely for validation.

def count_tokens_before_send(model_name: str, contents: list) -> int:
    """Count tokens before sending. count_tokens doesn't consume generation quota."""
    model = genai.GenerativeModel(model_name)
    token_count = model.count_tokens(contents)
    total = token_count.total_tokens
 
    if total > 500_000:
        print(f"⚠️ Large request: {total:,} tokens")
        print("  Expect increased latency")
 
    return total
 
count = count_tokens_before_send("gemini-2.5-pro-latest", ["Your long document..."])
print(f"Token count: {count:,}")

The same prompt that takes 2 seconds at 100K tokens might take 6–8 seconds at 500K. "Can fit" and "should fit" are not the same thing — something I only really learned by measuring.

Which Errors to Retry, and Which to Fix

Don't treat every error the same way. Some are resolved by retrying; others will never resolve until you fix the code. Retrying a malformed request just wastes quota.

Retry these — they're transient, caused by the server or by congestion:

  • 429 RESOURCE_EXHAUSTED — rate limit; use exponential backoff
  • 500 INTERNAL_SERVER_ERROR — transient server issue; wait briefly
  • 503 SERVICE_UNAVAILABLE — temporary disruption; wait a few minutes

Don't retry these — the cause is on the request side:

  • 400 INVALID_ARGUMENT — your request is malformed; fix the code
  • 403 PERMISSION_DENIED — API key lacks permissions
  • 404 NOT_FOUND — model name is wrong
  • StopCandidateException — blocked by safety filters; the prompt needs to change
def classify_error(error: Exception) -> str:
    error_str = str(error).lower()
 
    if "429" in error_str or "resource_exhausted" in error_str:
        return "rate_limit"       # Retryable
    elif "500" in error_str or "503" in error_str:
        return "server_error"     # Retryable
    elif "400" in error_str:
        return "invalid_request"  # Fix the code
    elif "403" in error_str:
        return "auth_error"       # Check API key
    else:
        return "unknown"

Putting this classification in one place early keeps your retry logic readable.

Usage Tracking for Cost Control

Alongside handling rate limits, visibility into usage is what pays off in production. If you can't see how many calls and tokens you're spending, you can't reason about either cost or error patterns.

import time
from dataclasses import dataclass, field
 
@dataclass
class UsageStats:
    """Track API usage across multiple calls."""
    requests: int = 0
    input_tokens: int = 0
    output_tokens: int = 0
    errors: int = 0
    start_time: float = field(default_factory=time.time)
 
    def add_response(self, response: genai.types.GenerateContentResponse):
        self.requests += 1
        if hasattr(response, "usage_metadata"):
            self.input_tokens += response.usage_metadata.prompt_token_count
            self.output_tokens += response.usage_metadata.candidates_token_count
 
    def print_summary(self):
        elapsed = time.time() - self.start_time
        # Gemini 2.5 Pro pricing (as of April 2026 — verify current rates in the docs)
        # Input: $1.25/1M tokens (under 200K), Output: $10/1M tokens
        input_cost = (self.input_tokens / 1_000_000) * 1.25
        output_cost = (self.output_tokens / 1_000_000) * 10.0
 
        print(f"=== Usage Summary ===")
        print(f"Elapsed: {elapsed:.1f}s")
        print(f"Requests: {self.requests} ({self.errors} errors)")
        print(f"Input tokens: {self.input_tokens:,}")
        print(f"Output tokens: {self.output_tokens:,}")
        print(f"Estimated cost: ${input_cost + output_cost:.4f}")
 
stats = UsageStats()
response = generate_with_retry("Your prompt")
if response:
    stats.add_response(response)
stats.print_summary()

Pricing changes, so confirm the coefficients against the official pricing page before relying on them.

Production Settings Often Overlooked

Even with retry logic in place, production needs a bit more.

Set explicit timeouts. By default, long requests never time out, so a hung request blocks resources indefinitely:

response = model.generate_content(
    contents,
    request_options={"timeout": 120}  # 120 second timeout
)

Have a model fallback. When Gemini 2.5 Pro is congested at peak hours, some tasks can be served by Flash at acceptable quality. Routing lower-priority requests to Flash by importance reduces both cost and exposure to Pro-tier rate limits.

Log everything at first. Once you can see which errors occur and how often, priorities sort themselves out. Production error distributions rarely match what you'd predict, and you can't fix what you can't see.

Rate Limits Aren't a Sign That Something Is Broken

A rate limit error doesn't mean Gemini API is broken. With sensible retry logic and request pacing, you can build a system that stays stable under production traffic. Know your application's RPM/TPM baseline first, design with headroom, and the limits stop being a problem.

As an indie developer, I run article generation for the Dolice Labs blogs and a few app backends through Gemini. Early on I'd push a batch straight through, hit 429 again and again, and stall each time. The moment the same job started flowing quietly once I added spacing and retries, it clicked: rate limits aren't an adversary, they're a constraint to design around. Log carefully, learn what your own traffic does, and that patient accumulation turns out to be the sturdiest fix of all.

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-05-04
Why Is My Gemini API Response Slow? A Practical Diagnosis Guide
Slow Gemini API responses and timeout errors can stem from 4 different causes. This guide walks you through diagnosing each one and applying targeted fixes that actually work.
API / SDK2026-06-22
Gemini API on Google Cloud: Diagnosing Production Errors Layer by Layer
Systematically diagnose Gemini API errors in Google Cloud production environments. Covers IAM permissions, Vertex AI vs AI Studio, VPC Service Controls, quota management, service accounts, and multi-region failover with full code examples.
API / SDK2026-06-21
How to Handle Gemini API Model Deprecation and Migration Errors
A practical guide to migrating from deprecated Gemini API models and resolving common migration errors.
📚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 →