●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Classify Gemini API Errors by Status — Handling 429, SAFETY, and Token Limits in Production
Fix common Gemini API errors including 429 rate limits, 400 bad requests, 401/403 authentication errors, and 500 server errors. A practical troubleshooting guide for developers getting started with Gemini API.
On the first day I wired the Gemini API into one of my indie apps, what cost me the most time wasn't the error codes themselves — it was deciding whether a given failure would clear up if I waited, or whether I had to go fix my code. A 429 tells you something is wrong, but not whether it's a rate limit or an auth problem until you read the status in the response.
This walkthrough organizes the errors you'll hit by their status and finish_reason, then turns them into handling that won't stall in production — with the code I actually use. The goal isn't a list of errors; it's being able to act without hesitation the moment one shows up.
Common Gemini API Errors
When working with the Gemini API, you'll likely run into one of these error codes:
400 Bad Request — Your request is malformed or missing required fields
401/403 Unauthorized/Forbidden — Authentication failed or credentials are invalid
429 Too Many Requests — You've hit the rate limit
500/503 Internal Server Error — Google's servers are experiencing issues
The good news is that once you understand what each error means, most of them are easy to fix.
400 Bad Request — Invalid Requests and Wrong Model Names
A 400 error means the API can't understand your request. The three most common causes are model name mistakes, malformed JSON, and input that's too long.
1. Using the wrong model name
Typos or outdated model names are the number-one cause of 400 errors. The Gemini API only accepts specific model names.
import google.generativeai as genaigenai.configure(api_key="YOUR_GEMINI_API_KEY")# ❌ Wrong: Model doesn't existmodel = genai.GenerativeModel("gemini-invalid-model")# ✅ Correct: Use a real model namemodel = genai.GenerativeModel("gemini-2.0-flash")response = model.generate_content("Hello, world!")print(response.text)
When using TypeScript or JavaScript, if your request body's JSON structure isn't correct, you'll get a 400 error. Make sure all required fields are present.
Pay special attention to required fields like contents, parts, and role. Missing any of these will cause a 400 error.
3. Input text exceeds maximum length
Very long text inputs can trigger a 400 error. Each Gemini model has a context window limit (the maximum amount of text you can send):
gemini-2.0-flash — Up to 1 million tokens
If you need to process large amounts of text, split it into smaller chunks and make multiple requests.
✦
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
✦A single error handler that classifies exceptions by status and finish_reason to decide retry vs. fail
✦Splitting 429s across RPM, RPD, and TPM, with jittered exponential backoff to avoid retry storms
✦Turning empty responses, SAFETY, RECITATION, and context overflow into cause-specific fixes
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.
You'll need to enable the Vertex AI API in your GCP project. Check the Vertex AI documentation for detailed setup instructions.
429 Too Many Requests — Rate Limiting
You'll get a 429 error if you send too many requests in a short time. The Gemini API has different rate limits depending on whether you're using the free tier or a paid plan.
Free tier rate limits
When using Google AI Studio for free:
Maximum 60 requests per minute
Daily request limits vary
Paid tier rate limits
With a Google Cloud paid plan:
Maximum 360 requests per minute (standard)
Varies by plan and model
Exponential backoff strategy
When you hit a 429 error, don't immediately retry. Instead, use exponential backoff — gradually increasing wait times between retries.
import timeimport google.generativeai as genaigenai.configure(api_key="YOUR_GEMINI_API_KEY")model = genai.GenerativeModel("gemini-2.0-flash")def generate_with_retry(prompt, max_retries=5): """Retry logic with exponential backoff""" for attempt in range(max_retries): try: response = model.generate_content(prompt) return response.text except Exception as e: if "429" in str(e): # Wait: 1s, 2s, 4s, 8s, 16s... wait_time = 2 ** attempt print(f"Rate limited. Waiting {wait_time} seconds...") time.sleep(wait_time) else: raise# Usageresult = generate_with_retry("What is the capital of France?")print(result)
Here's the TypeScript equivalent:
import { GoogleGenerativeAI } from "@google/generative-ai";const genAI = new GoogleGenerativeAI("YOUR_GEMINI_API_KEY");const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });async function generateWithRetry(prompt: string, maxRetries = 5): Promise<string> { for (let attempt = 0; attempt < maxRetries; attempt++) { try { const response = await model.generateContent(prompt); return response.response.text(); } catch (error) { if (error instanceof Error && error.message.includes("429")) { const waitTime = Math.pow(2, attempt) * 1000; // Convert to milliseconds console.log(`Rate limited. Waiting ${waitTime / 1000} seconds...`); await new Promise((resolve) => setTimeout(resolve, waitTime)); } else { throw error; } } } throw new Error("Max retries exceeded");}// Usageconst result = await generateWithRetry("What is the capital of France?");console.log(result);
Which Limit Are You Hitting — RPM, RPD, or TPM?
A 429 doesn't have a single cause. Gemini API enforces three independent limits:
RPM (Requests Per Minute) — how many requests you send each minute
RPD (Requests Per Day) — total requests per day
TPM (Tokens Per Minute) — tokens processed per minute
As an indie developer, the one that caught me first was TPM. Even with few requests, sending long prompts back to back hits the per-minute token ceiling before the request count does. Working out which limit you've reached is the starting point for any fix.
Is RESOURCE_EXHAUSTED Really a Quota Problem?
A 429 response carries RESOURCE_EXHAUSTED in its body:
{ "error": { "code": 429, "message": "Resource has been exhausted (e.g. check quota).", "status": "RESOURCE_EXHAUSTED" }}
RESOURCE_EXHAUSTED means you've hit a quota, and waiting will help. By contrast, INVALID_ARGUMENT and PERMISSION_DENIED are request-format or authentication problems — waiting won't fix them. Check the status first to decide whether to wait or to fix the request.
How Different Are Free and Paid Quotas?
The free tier is meant for development and testing, not production. Rough guidance follows (exact numbers vary by model, region, and over time, so confirm the latest in the official docs):
Free tier — RPM in the tens per minute, RPD in the low thousands per day, TPM around 10,000 tokens, about 2 concurrent sessions
Paid (pay-as-you-go) — RPM up to several thousand per minute, effectively no RPD cap, TPM from 100,000 to 1,000,000 tokens, around 100 concurrent sessions
The easy thing to miss: these limits are shared per project, not per user. If a test script and your production app hit the API under the same project, they add up against the same ceiling. In my own app I once left a test batch running and watched production start returning 429s — it took a while to trace.
As a rough scale guide: under ~10k requests/month, the free tier plus a spend limit is fine; up to ~1M, pay-as-you-go; beyond that, talk to Google Cloud about volume pricing.
Checking and Raising Your Quota
You can see current usage in the Google Cloud Console:
In Google Cloud Console, open "APIs & Services", select Generative Language API, and check "Quotas & System Limits"
Add billing details under "Billing" to switch to paid limits automatically (it can take minutes to hours to take effect)
Avoiding Surprise Bills
Before moving to paid limits, set up a budget alert. Under "Billing" then "Budgets" in the Google Cloud Console, set a monthly cap and enable an alert (for example, notify at 100% of budget). Stopping calls when you hit the cap protects you from unexpected charges.
Watching Your Usage
In production, noticing a limit after you've hit it is too late. Monitor quota_used_count in Cloud Monitoring and alert when usage crosses 80%, so you can decide on a paid upgrade or a retry strategy ahead of time.
displayName: Gemini API quota alertconditions: - displayName: RPM usage > 80% conditionThreshold: filter: | resource.type="api" AND metric.type="serviceruntime.googleapis.com/api/consumer/quota_used_count" AND resource.service="generativelanguage.googleapis.com" threshold_value: 0.8 comparison: COMPARISON_GT
500/503 Server Errors — Google's Infrastructure Issues
When you get a 500 (Internal Server Error) or 503 (Service Unavailable) error, Google's servers are experiencing temporary issues. This isn't a problem with your code or credentials.
If the problem persists, contact Google Cloud Support
import timedef generate_with_500_retry(prompt, max_retries=3): """Retry logic for server errors""" for attempt in range(max_retries): try: response = model.generate_content(prompt) return response.text except Exception as e: error_code = str(e) if "500" in error_code or "503" in error_code: wait_time = 5 * (attempt + 1) # 5s, 10s, 15s print(f"Server error. Retrying in {wait_time} seconds...") time.sleep(wait_time) else: raise
Context Window Overflow — Handling INVALID_ARGUMENT and Token Limits
When you pass a long document or several images at once, you may hit a 400-class error like this:
google.api_core.exceptions.InvalidArgument: 400 Request payload size exceeds the limit
With multimodal requests, it can surface as:
Invalid request: the total input token count exceeds the model's limit
It happens when your input exceeds the model's context window. Rather than reacting after the fact, count your tokens before sending.
import google.generativeai as genaiimport osgenai.configure(api_key=os.environ["GOOGLE_API_KEY"])model = genai.GenerativeModel("gemini-2.0-flash")def count_tokens(prompt: str) -> int: # Check the token count before sending return model.count_tokens(prompt).total_tokensdef safe_generate(prompt: str, max_chars: int = 30000) -> str: if len(prompt) > max_chars: # Trimming the middle preserves more context than cutting the tail half = max_chars // 2 prompt = prompt[:half] + "\n...[truncated]...\n" + prompt[-half:] return model.generate_content(prompt).text
Cutting only the tail tends to lose your conclusion. Keeping the head and tail while dropping the middle leaves both your instructions and your closing intact.
When you truly need the whole thing, switch to the 1M-token context of Gemini 2.5 Pro or Flash, or split the document into meaningful chunks and feed them in sequence.
Empty or Blocked Responses — Diagnosing with finish_reason
No obvious error, yet no text comes back. This is a surprisingly common stumbling point.
response = model.generate_content("...")print(response.text) # AttributeError: 'NoneType' object has no attribute 'text'
Instead of reaching straight for response.text, check whether candidates exists and inspect finish_reason first — the fix depends on the cause.
def safe_get_text(response) -> str | None: # No candidates means the input was blocked if not response.candidates: print(f"Input was blocked: {response.prompt_feedback}") return None candidate = response.candidates[0] reason = candidate.finish_reason.name if reason == "SAFETY": print(f"Blocked by safety filter: {candidate.safety_ratings}") return None if reason == "RECITATION": print("Blocked due to recitation of copyrighted content") return None if reason == "MAX_TOKENS": # Partial text is still available print("Reached max_output_tokens. Raise the limit.") try: return candidate.content.parts[0].text except (IndexError, AttributeError): return None
For SAFETY, rephrase the prompt in more neutral terms. RECITATION appears when the model is asked to reproduce existing text verbatim, so request a summary or paraphrase instead. MAX_TOKENS means raising max_output_tokens. Once each cause maps to a fix, empty responses stop being a mystery.
You can tune filter sensitivity through safety_settings, but in most cases simply revising the wording of your prompt resolves it. Start by reviewing the content of your input prompt.
Classifying Every Exception in One Place
Scattering try/except blocks across each call site means coverage gaps grow as the codebase grows. Once I started running recovery batches daily, I settled on routing every API call through a single wrapper that branches on status and finish_reason in one place.
The idea is simple. Sort exceptions into three groups: "will recover if you wait" (retry), "won't recover until you fix it" (fail fast), and "partially usable" (warn and continue).
import timeimport randomimport google.generativeai as genaifrom google.api_core import exceptions as gaxgenai.configure(api_key="YOUR_GEMINI_API_KEY")model = genai.GenerativeModel("gemini-2.0-flash")# States likely to recover if you waitRETRYABLE = (gax.ResourceExhausted, gax.ServiceUnavailable, gax.InternalServerError)# States that won't recover until you fix the requestFATAL = (gax.InvalidArgument, gax.PermissionDenied, gax.Unauthenticated)def call_gemini(prompt, max_retries=5, base=1.0): for attempt in range(max_retries): try: return model.generate_content(prompt) except RETRYABLE as e: # Exponential backoff + jitter (avoids synchronized retries) wait = base * (2 ** attempt) + random.uniform(0, base) print(f"[retry {attempt + 1}] {type(e).__name__}: waiting {wait:.1f}s") time.sleep(wait) except FATAL as e: # Waiting here is wasted time. Return to the caller immediately. raise RuntimeError(f"Needs a fix: {type(e).__name__}: {e}") from e raise RuntimeError("Exceeded max retries")
The jitter (random.uniform) is there for a reason. When several workers hit a 429 at once, a fixed wait lines up their retries, so the instant the quota recovers they all collide again. Adding a little randomness measurably reduced those re-collisions for me.
Status-to-Action Table
So you have something to fall back on when you're stuck, here's the status and the move to make, on one page.
status / symptom
meaning
action
retry?
RESOURCE_EXHAUSTED (429)
RPM / RPD / TPM exceeded
Backoff + jitter, raise quota
yes
INVALID_ARGUMENT (400)
Bad model name / JSON / input length
Fix the request
no
UNAUTHENTICATED (401)
Missing or invalid API key
Check key and env vars
no
PERMISSION_DENIED (403)
API not enabled / no permission
Check project settings
no
INTERNAL / UNAVAILABLE (500/503)
Temporary issue on Google's side
Wait and retry, check status
yes
finish_reason=SAFETY
Blocked by safety filter
Reword the prompt neutrally
no
finish_reason=MAX_TOKENS
Hit output limit
Raise max_output_tokens
conditional
The column that earns its keep is "retry?" on the right. Retrying a FATAL case keeps the user waiting only to fail anyway. Just deciding up front whether a failure is worth waiting on changes the response your users feel.
Other Common Errors
Using a deprecated model
If you're following old tutorials or code, you might be using a model that's no longer available. Older models like gemini-1.5-pro and gemini-1.5-flash have been replaced by newer versions like gemini-2.0-pro and gemini-2.0-flash.
Regional restrictions
Some models and APIs are only available in certain regions. If you're using Vertex AI, make sure you're calling the API in the same region as your project.
echo $GOOGLE_API_KEY — is the variable set and does the value look right?
Model name — does it exactly match the official list?
Token count — use model.count_tokens() before sending large requests
Response structure — check response.candidates before accessing .text
Rate limits — add time.sleep(1) between requests in loops, add retry logic
These five checks resolve the vast majority of Gemini API issues that come up during development.
Where to go next
The point of error handling isn't memorizing a list — it's being able to look at the status and finish_reason and instantly decide whether to wait or to fix. Start by routing your existing API calls through the single handler above. With branching in one place, adding support for a new error becomes a one-line change.
If you still get stuck, here are the official resources:
If this saves even a little of the time of someone stuck on the same thing, I'm glad.
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.