# Nano Banana API Troubleshooting: 400, 429, 503, NO_IMAGE, Timeouts

> Separate request-level HTTP errors, generation finish reasons, and caller timeouts before you repair, inspect limits, or retry; includes parsing code and escalation evidence.

- Source: https://www.aifreeapi.com/en/posts/nano-banana2-api-error-codes
- Language: en
- Published: 2026-08-05
- Updated: 2026-08-05
- Publisher: AI Free API (https://www.aifreeapi.com)

If a Nano Banana image request fails, preserve the raw evidence before retrying. The decisive question is which layer produced the signal: `400/429/503` are request-level HTTP results, `NO_IMAGE` is an official generation finish reason in the current Google API reference, and a timeout can mean only that your caller or gateway stopped waiting. Those branches need different actions.

| Symptom | Evidence to capture first | First action | Retry unchanged? | Recovery proof |
|---|---|---|---|---|
| `400 INVALID_ARGUMENT` | Final URL, API version, model, redacted body, error details | Correct the request contract | No | The same minimal request no longer returns 400 |
| `429 RESOURCE_EXHAUSTED` | Canonical status, details, current rate-limit view, attempt and time | Identify the constrained dimension and smooth traffic | Bounded only | Stable success under controlled concurrency |
| `503 UNAVAILABLE` | Request ID, model, route, start/end time | Check service health and back off | Bounded only | Same-route minimal probe succeeds |
| `finishReason=NO_IMAGE` | Full candidate, `finishMessage`, parts, response ID | Check output modality and run a minimal probe | Classify first | A new response contains a decodable image part |
| Timeout or `504` | Per-hop deadlines, elapsed time, whether a body arrived, request ID | Find the first closing layer and check unknown outcome | Transient branches only | Same-route completion without duplicate output |

Google's current [Gemini API troubleshooting guide](https://ai.google.dev/gemini-api/docs/troubleshooting) recommends bounded exponential backoff for transient errors such as 408, 429, and 5xx, while 400/403 should not be retried blindly. The [API errors reference](https://ai.google.dev/gemini-api/docs/api-errors) lists `no_image` as a generation error, and the [GenerateContent reference](https://ai.google.dev/api/generate-content) defines `NO_IMAGE` as a `FinishReason`: an image was expected, but none was generated. It is official, but it is not an HTTP status and does not by itself prove safety filtering, overload, or billing behavior.

## Preserve the evidence before a wrapper flattens it

Many incidents become untraceable because an adapter turns a detailed response into one exception string. Log the following fields before applying user-facing error mapping, with prompts, input images, and credentials redacted:

```text
timestamp_utc, provider, base_url, api_version, endpoint, model
http_status, canonical_status, request_id, upstream_request_id
attempt, started_at, latency_ms, client_timeout_ms
candidate_count, prompt_block_reason, finish_reason
part_mime_types, has_text_part, has_image_part, error_body_summary
```

“Nano Banana” is the common nickname, not a protocol identifier. Record the actual route. A Gemini Developer API call, a Vertex AI call, a third-party gateway, and your own reverse proxy can produce similar surface symptoms with different responsibility boundaries. This guide uses the Gemini Developer API's native `models/{model}:generateContent` shape unless a section explicitly says otherwise.

Use one harmless, single-goal image request as the same-route probe. Do not copy a model ID from an old article; set `GEMINI_MODEL` to an image model that the current official model page shows as available for your account.

```bash
API_VERSION="${GEMINI_API_VERSION:-v1beta}"
MODEL="${GEMINI_MODEL:?set GEMINI_MODEL}"

curl --fail-with-body --silent --show-error \
  -X POST \
  "https://generativelanguage.googleapis.com/${API_VERSION}/models/${MODEL}:generateContent" \
  -H "x-goog-api-key: ${GEMINI_API_KEY:?set GEMINI_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "contents": [{"parts": [{"text": "Generate a product photo of a blue ceramic mug on a white background."}]}],
    "generationConfig": {"responseModalities": ["IMAGE"]}
  }'
```

If this probe succeeds while the original request fails, the fault domain has narrowed to input, context, or optional parameters. If it also fails, continue with account, capacity, route, and platform evidence.

![Three-layer map of Nano Banana HTTP errors, generation finish reasons, and caller timeouts](https://www.aifreeapi.com/posts/en/nano-banana2-api-error-codes/img/response-layers.webp)

## A 400 is a diff problem, not a retry problem

`400 INVALID_ARGUMENT` means the server rejected the request contract. Common categories include malformed JSON, a field unsupported by the selected API version, a model that does not support the requested feature, or invalid image input metadata. Sending the identical body again only adds noise.

Reduce the request in a controlled order:

1. Record the final URL after configuration is resolved. Confirm that a native Gemini Developer API path was not mixed with a Vertex project/region path.
2. Compare the model, API version, output modalities, and image options against the current [image generation documentation](https://ai.google.dev/gemini-api/docs/generate-content/image-generation).
3. Remove optional fields until only one text part and an `IMAGE` output request remain.
4. Add one field back per test. A successful minimal request plus one failing addition is stronger evidence than a large “correct-looking” body.
5. For image inputs, log the actual MIME type, transfer method, and byte size rather than trusting the filename extension.

Do not mark the incident fixed because the error message changed. The proof is a response from the same endpoint that contains a valid image part.

## Diagnose 429 and 503 separately, then share the retry machinery

A `429 RESOURCE_EXHAUSTED` means one of the applicable usage or rate constraints has been exceeded. The relevant dimension can include RPM, TPM, RPD, spend, or another model/tier limit. Those values change. Check the current values in [Google AI Studio's rate-limit view](https://ai.google.dev/gemini-api/docs/rate-limits) instead of relying on a number copied into application code or documentation.

Look for bursts before requesting more quota. Queue work, cap worker concurrency, and spread requests over time. If many workers wake on the same delay, they can create a retry storm even after capacity returns.

A `503 UNAVAILABLE` means the service may be overloaded or temporarily interrupted. It is not evidence that the prompt is invalid, and there is no universal recovery window. Compare a minimal probe on the same account/model/route, inspect official service health, and stop automated retries when the application's attempt or elapsed-time budget is exhausted. Persistent 503s need a request ID and time window for provider support.

Use one retry layer. An SDK may already retry transient failures; stacking another loop outside it can multiply calls unexpectedly. For custom REST clients, bound the per-attempt timeout, attempt count, and total elapsed budget:

```python
import random
import time
import requests

TRANSIENT_STATUS = {408, 429, 500, 502, 503, 504}

def post_with_bounded_retry(
    url, headers, payload, *, max_attempts=4, total_budget_s=120
):
    deadline = time.monotonic() + total_budget_s

    for attempt in range(1, max_attempts + 1):
        remaining = deadline - time.monotonic()
        if remaining <= 0:
            raise TimeoutError("retry budget exhausted")

        try:
            response = requests.post(
                url,
                headers=headers,
                json=payload,
                timeout=(10, min(60, remaining)),
            )
        except (requests.Timeout, requests.ConnectionError):
            if attempt == max_attempts:
                raise
        else:
            if response.status_code == 400:
                raise ValueError(f"fix request before retry: {response.text[:500]}")
            if response.status_code not in TRANSIENT_STATUS:
                response.raise_for_status()
                return response.json()
            if attempt == max_attempts:
                response.raise_for_status()

        base = min(2 ** (attempt - 1), 30)
        delay = base + random.uniform(0, base * 0.5)
        if delay >= deadline - time.monotonic():
            raise TimeoutError("no retry fits inside total budget")
        time.sleep(delay)

    raise RuntimeError("unreachable")
```

The exact cap is an application decision, not a provider promise. Bound both attempts and total elapsed time so a retry cannot outlive the user's request or duplicate work after the caller has moved on.

## `NO_IMAGE` is a generation result, not an HTTP error

Do not conflate the official finish reason with an application's generic “no image” message. In the current Google contract, `NO_IMAGE` belongs to a candidate's `finishReason`; a gateway may separately map it to lowercase `no_image`. Preserve the upstream body and walk it from the outside in:

- No `candidates`: inspect `promptFeedback`. A prompt problem can prevent candidates from being returned.
- `finishReason=NO_IMAGE`: record `finishMessage`, `responseId`, and `modelVersion`, confirm the requested output modality, and run the harmless same-route probe. Do not relabel it as safety or 503 without evidence.
- `IMAGE_SAFETY`, `IMAGE_PROHIBITED_CONTENT`, or another policy finish reason: review a legitimate request or accept the boundary; retries are not a bypass.
- Text parts but no image part: read the text and verify output modalities. Text-only output is not the same evidence as `NO_IMAGE`.
- An `inlineData` image part exists but the app still says no image: inspect adapter parsing, MIME validation, base64 decoding, and storage writes.

A defensive parser should keep image success, official `NO_IMAGE`, policy stops, and other image-less candidates separate:

```python
def inspect_generate_content(body):
    feedback = body.get("promptFeedback")
    candidates = body.get("candidates") or []
    if not candidates:
        return {"kind": "no_candidates", "promptFeedback": feedback}

    candidate = candidates[0]
    finish_reason = candidate.get("finishReason")
    parts = ((candidate.get("content") or {}).get("parts") or [])
    images = [part["inlineData"] for part in parts if part.get("inlineData")]
    texts = [part["text"] for part in parts if part.get("text")]

    if images:
        kind = "image"
    elif finish_reason == "NO_IMAGE":
        kind = "no_image"
    elif finish_reason in {
        "SAFETY", "IMAGE_SAFETY", "PROHIBITED_CONTENT",
        "IMAGE_PROHIBITED_CONTENT", "IMAGE_RECITATION",
    }:
        kind = "blocked_or_policy"
    else:
        kind = "candidate_without_image"

    return {
        "kind": kind,
        "finishReason": finish_reason,
        "finishMessage": candidate.get("finishMessage"),
        "imageCount": len(images),
        "textCount": len(texts),
        "responseId": body.get("responseId"),
        "modelVersion": body.get("modelVersion"),
    }
```

For a deeper response-side branch, see the [Gemini no-image and IMAGE_SAFETY guide](/en/posts/gemini-image-silent-failure-image-safety-fix). If authentication and other Gemini HTTP errors are mixed into the incident, use the broader [Gemini API error troubleshooting guide](/en/posts/gemini-api-error-troubleshooting-guide).

## Timeouts: identify the first layer that closed

A typical request crosses several deadlines:

```text
client/SDK → application server → reverse proxy/API gateway → provider → model
```

A client cancellation may leave no Google HTTP body at all. A reverse proxy can generate its own 504, so record a provider 504 only when it arrived in the upstream response. The inverse matters too: a caller timeout does not prove the server stopped. Before retrying work that can create an image or downstream write, query task records or artifact storage by request/response ID so you do not duplicate an output. Track start time, time to first byte, end time, configured deadline, and closing layer for every hop you control.

Run two comparisons through the same account, route, and model: the harmless minimal probe and the original complex input. If the probe succeeds and the complex request consistently ends at a client deadline, change the correct timeout or reduce the request. If both fail with the same 503/504 window, repeatedly sending the larger request adds little diagnostic value.

Raising every timeout is not a fix. It can merely make users wait longer while an upstream capacity issue, proxy idle timeout, or stuck worker remains unchanged.

![Bounded retry timeline for Nano Banana transient errors, stop conditions, and escalation](https://www.aifreeapi.com/posts/en/nano-banana2-api-error-codes/img/retry-timeline.webp)

## Stop conditions and the support packet

Stop automatic retries when the response is a 400 or another known non-retryable client error, when `NO_IMAGE` has not yet been classified with a minimal probe and raw response, when a safety/policy finish reason is present, when the attempt or elapsed-time budget is exhausted, when an identical request fingerprint keeps returning 5xx, or when the caller can no longer use the result.

Send support a redacted incident packet instead of a screenshot of the final UI message:

- UTC time window, provider/base URL, API version, endpoint, and model;
- HTTP code, canonical status, and full redacted error details;
- request ID and upstream request ID;
- per-attempt latency, total elapsed time, and timeout configuration;
- minimal safe prompt, input-image MIME information, and output modalities;
- candidate count, `promptFeedback`, `finishReason`, `finishMessage`, and part MIME types;
- `responseId`, `modelVersion`, and whether a timed-out call later produced an artifact;
- result of the same-account, same-route, same-model minimal probe.

The final verification is narrow and observable: the minimal request returns a decodable image part on the route you actually use, then the original workload completes under controlled concurrency. A 200 status, a queue marked complete, or the disappearance of an adapter's `NO_IMAGE` label is not sufficient by itself.
