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

Diagnosing INVALID_ARGUMENT Errors in Gemini API Batch Image Analysis

When using the Gemini API to analyze multiple images at once, INVALID_ARGUMENT errors can be surprisingly hard to diagnose. This guide covers the three root causes—MIME type mismatches, inline data size limits, and contents structure errors—with code to fix each one.

gemini-api277troubleshooting82image5python104invalid-argument

While building the automated wallpaper categorization feature for Beautiful HD Wallpapers (iOS/Android, 50 million+ downloads cumulative), I kept running into INVALID_ARGUMENT errors from the Gemini API when processing images in batches. My first instinct was to double-check the API key—but the real cause was somewhere else entirely.

Having been a solo app developer since 2014, I've spent more hours than I'd like to admit chasing down cryptic API errors. Here's the diagnostic process I wish I'd had from the start.

Why INVALID_ARGUMENT Is So Vague

INVALID_ARGUMENT (HTTP 400) follows Google Cloud's convention: something in the request is wrong, but the top-level message won't tell you exactly what. For image-related requests, the root cause almost always falls into one of three buckets:

  • MIME type mismatch — for example, sending a PNG file with image/jpeg declared
  • Inline data size exceeded — base64-encoded data over 20 MB per request
  • Malformed contents array — wrong structure for multi-part requests

Let's walk through each one with the diagnostic and fix.

Cause 1: MIME Type Mismatch

Hardcoding MIME types based on file extension is the most common trap. A file named .jpg may actually be a WebP or PNG on the inside—especially when dealing with wallpaper images downloaded from various sources, which is exactly the situation I ran into.

import imghdr
from pathlib import Path
 
def get_mime_type(image_path: str) -> str:
    """Detect MIME type from file content, not extension."""
    detected = imghdr.what(image_path)
    
    mime_map = {
        "jpeg": "image/jpeg",
        "png": "image/png",
        "webp": "image/webp",
        "gif": "image/gif",
    }
    
    if detected and detected in mime_map:
        return mime_map[detected]
    
    # Fallback to extension-based detection
    path = Path(image_path)
    ext = path.suffix.lower()
    fallback = {
        ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
        ".png": "image/png", ".webp": "image/webp"
    }
    return fallback.get(ext, "image/jpeg")
 
# Usage
mime = get_mime_type("/path/to/image.jpg")
print(f"Detected MIME: {mime}")
# → Detected MIME: image/jpeg

imghdr is part of the Python standard library, so no extra install needed. Note that Python 3.13+ deprecates imghdr—if you're on a recent Python version, python-magic is a solid drop-in replacement.

After switching to content-based detection for the wallpaper app, the INVALID_ARGUMENT rate dropped noticeably. Images that looked like JPEGs by filename turned out to be WebP files—a mismatch that the API rejected silently until I caught it in the exception message.

Cause 2: Inline Data Size Limit

The Gemini API enforces a 20 MB limit on inline data per request. Base64 encoding inflates file size by roughly 33%, so the practical ceiling for image files is around 15 MB.

4K wallpaper images can easily exceed 10–20 MB each. Trying to send 10 of them in a single batch request means 100+ MB of payload—well beyond what the API will accept.

import os
 
def check_image_size(image_path: str) -> dict:
    """Check whether an image should be sent inline or via File API."""
    file_size = os.path.getsize(image_path)
    base64_size = file_size * 4 / 3  # estimated after base64 encoding
    
    return {
        "file_path": image_path,
        "file_size_mb": round(file_size / (1024 * 1024), 2),
        "estimated_base64_mb": round(base64_size / (1024 * 1024), 2),
        "use_file_api": base64_size > 15 * 1024 * 1024,
    }
 
# Example output
result = check_image_size("/path/to/large_wallpaper.jpg")
print(result)
# → {'file_size_mb': 12.4, 'estimated_base64_mb': 16.5, 'use_file_api': True}

Running this check before the batch starts prevents the frustrating situation where the API processes 8 out of 10 images before failing on the 9th. Any image where use_file_api is True should go through the File API instead.

Cause 3: Malformed contents Structure

When sending multiple images, the structure of the contents array matters. This mistake is especially common when calling the REST API directly rather than through the Python SDK.

# Wrong: text and image_data as separate top-level dicts
contents = [
    {"text": "Classify each image by category"},
    {"inline_data": {"mime_type": "image/jpeg", "data": encoded_img}},
]
 
# Correct: parts list inside a single content dict
contents = [
    {
        "parts": [
            {"text": "Classify each image by category"},
            {"inline_data": {"mime_type": "image/jpeg", "data": encoded_img}},
        ]
    }
]

The Python SDK handles this automatically when you pass a list of strings and image objects, but calling the REST API directly requires the correct nested structure. I hit this issue when I temporarily bypassed the SDK for a performance experiment—cost me about two hours before I spotted it.

Choosing Between File API and Inline Data

Here's a complete implementation that selects the right approach based on file size, combining all three fixes:

import google.generativeai as genai
import base64
import time
import os
 
def upload_or_encode_image(image_path: str, mime_type: str) -> dict:
    """
    Route to File API for large images, inline data for small ones.
    - 15 MB or smaller: inline_data (lower latency)
    - Larger than 15 MB: File API (async upload, referenced by URI)
    """
    file_size = os.path.getsize(image_path)
    
    if file_size <= 15 * 1024 * 1024:
        with open(image_path, "rb") as f:
            data = base64.b64encode(f.read()).decode("utf-8")
        return {"inline_data": {"mime_type": mime_type, "data": data}}
    
    else:
        uploaded = genai.upload_file(image_path, mime_type=mime_type)
        
        # Wait until the file is active (usually a few seconds to a minute)
        while uploaded.state.name == "PROCESSING":
            time.sleep(5)
            uploaded = genai.get_file(uploaded.name)
        
        if uploaded.state.name == "FAILED":
            raise ValueError(f"File upload failed: {uploaded.name}")
        
        return {"file_data": {"mime_type": mime_type, "file_uri": uploaded.uri}}
 
 
def analyze_images_batch(image_paths: list, prompt: str) -> str:
    """Safely analyze multiple images in a single request."""
    model = genai.GenerativeModel("gemini-2.5-flash")
    
    parts = [{"text": prompt}]
    
    for path in image_paths:
        mime = get_mime_type(path)
        part = upload_or_encode_image(path, mime)
        parts.append(part)
    
    response = model.generate_content({"parts": parts})
    return response.text
 
 
# Example
images = [
    "/wallpapers/nature_4k.jpg",
    "/wallpapers/city_night.webp",
]
result = analyze_images_batch(
    images,
    "Return the category for each image as JSON: [{category, subcategory}]"
)
print(result)
# → [{"category": "nature", "subcategory": "landscape"}, {"category": "city", "subcategory": "night"}]

For the wallpaper app, this pattern handled around 30,000 images: 4K originals went through File API, thumbnails stayed inline. INVALID_ARGUMENT errors essentially disappeared.

Reading the Error Message

When the exception occurs, catching and printing the message gives a strong hint about which of the three causes is at play:

import google.api_core.exceptions
 
try:
    response = model.generate_content(contents)
except google.api_core.exceptions.InvalidArgument as e:
    print(f"Message: {e.message}")
    # "Request payload size exceeds the limit"   → size problem
    # "Invalid MIME type"                         → MIME type problem
    # "Invalid value at 'contents[0].parts'"      → structure error
    # "Could not process Image:"                  → image read failure

In most cases, one of these four messages points directly to the fix needed.

Diagnostic Checklist

When INVALID_ARGUMENT appears on a batch image request, work through these steps in order:

  • Run check_image_size() on all images upfront and identify anything over 15 MB
  • Verify MIME type is detected from file content, not just the file extension
  • If calling the REST API directly, confirm the parts list is nested inside a single contents entry
  • Catch the exception and print e.message to read the specific cause

The safest approach when setting this up for the first time: start with a single small image (under 1 MB), confirm it works, then gradually increase batch size and image dimensions. Trying to debug a batch of 50 high-resolution images all at once makes it much harder to isolate the cause.

I hope this saves someone the hours I spent tracking down these errors.

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-04-30
Why Gemini Says It Cannot See Your Image — A Practical Diagnosis Guide
If Gemini API replies 'I don't see an image' despite an attached file, the cause is almost always client-side. This guide walks through the four checks — mime_type, payload size, SDK version, and model selection — with copy-pasteable fixes.
API / SDK2026-06-21
Gemini API Implicit Caching Not Working — Troubleshooting Guide by Root Cause
Troubleshoot Gemini API implicit caching issues: cache not hitting, unexpectedly high costs, or low cache hit rates. Covers token thresholds, prompt structure, model version consistency, TTL expiry, and multimodal caching with code examples.
API / SDK2026-06-14
When Gemini API Cuts Your Response Off Mid-Sentence — Detecting finish_reason: MAX_TOKENS and Stitching the Continuation
Long-form generation that ends mid-sentence is usually finish_reason: MAX_TOKENS. This failure arrives as a quiet HTTP 200, no exception. Here is how to detect it, stitch a continuation to recover the full text, and avoid the thinking-token trap that makes it worse on 3.x models.
📚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 →