AIFreeAPI Logo

Diagnóstico de Nano Banana API: 400, 429, 503, NO_IMAGE y timeout

A
5 min readSolución de problemas de API

Corrige 400, limita el retry transitorio, interpreta NO_IMAGE como finishReason y comprueba si un timeout dejó un resultado desconocido en el servidor.

400, 429, 503, NO_IMAGE y timeout distribuidos en tres capas de evidencia

Cuando falla una solicitud de imagen de Nano Banana, conserva la evidencia cruda antes de reintentar. 400/429/503 son resultados HTTP de la petición, NO_IMAGE es un motivo de finalización oficial en la referencia actual de Google, y un timeout puede significar solo que el cliente o gateway dejó de esperar. No deben entrar en el mismo bucle de retry.

SíntomaEvidencia que debes guardarPrimera acción¿Repetir igual?Prueba de recuperación
400 INVALID_ARGUMENTURL final, API version, modelo, body anonimizado, detailsCorregir el contrato de peticiónNoLa petición mínima deja de devolver 400
429 RESOURCE_EXHAUSTEDStatus/details, límites actuales, hora, intentoIdentificar el límite y suavizar tráficoSolo con límiteÉxito estable con concurrencia controlada
503 UNAVAILABLERequest ID, ruta, modelo, intervaloConsultar estado y aplicar backoffSolo con límiteEl probe mínimo funciona en la misma ruta
finishReason=NO_IMAGECandidate, finishMessage, parts, response IDRevisar modalidad y ejecutar probe mínimoTras clasificarNueva respuesta con parte de imagen decodificable
timeout o 504Deadlines por capa, duración, presencia de body, request IDHallar la primera capa y comprobar resultado inciertoSolo si es transitorioFinaliza por la misma ruta sin duplicado

La guía oficial de Gemini API recomienda backoff exponencial limitado para errores transitorios como 408, 429 y 5xx, y no reintentar 400/403 a ciegas. API errors incluye no_image como error de generación; GenerateContent define NO_IMAGE como FinishReason: se esperaba una imagen, pero no se generó. Es un resultado oficial, no un estado HTTP, y por sí solo no demuestra safety, sobrecarga ni facturación.

Conserva la respuesta antes de que el adaptador la simplifique

Registra estos campos antes de convertir el error en un mensaje para el usuario. Anonimiza prompt, imágenes de entrada y credenciales.

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 es un nombre común, no un identificador de protocolo. Guarda la ruta real. Gemini Developer API, Vertex AI, un gateway externo y tu reverse proxy pueden mostrar el mismo síntoma con responsabilidades diferentes. El probe siguiente usa la forma nativa de Gemini Developer API models/{model}:generateContent; no la mezcles con rutas Vertex de project/location.

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": "Genera una foto de producto de una taza de cerámica azul sobre fondo blanco."}]}], "generationConfig": {"responseModalities": ["IMAGE"]} }'

Configura GEMINI_MODEL con un modelo de imagen que la documentación oficial actual muestre como disponible para tu cuenta. Si el probe funciona y la petición original no, el problema se reduce a entrada, contexto o parámetros opcionales. Si ambos fallan, revisa cuenta, ruta y capacidad.

Capas de error HTTP, motivo de finalización y timeout del llamador en Nano Banana API
Capas de error HTTP, motivo de finalización y timeout del llamador en Nano Banana API

Un 400 se arregla comparando la petición

400 INVALID_ARGUMENT significa que la API ha rechazado la estructura o los parámetros. Repetir el mismo body no ayuda. Compara URL final, API version, modelo, responseModalities y MIME de entrada con la documentación actual de generación de imágenes.

Elimina campos opcionales hasta dejar una parte de texto y salida IMAGE. Después añade un campo por prueba. Un mínimo que funciona y falla al incorporar un parámetro es una evidencia mucho mejor que un payload grande que “parece correcto”. Si envías una imagen, registra el MIME real, método de transferencia y tamaño en bytes, no solo la extensión del fichero.

429 y 503 comparten backoff, no causa

429 RESOURCE_EXHAUSTED indica que has superado uno de los límites aplicables. Puede ser RPM, TPM, RPD, gasto u otro límite de modelo/nivel. Los valores cambian: compruébalos en Google AI Studio rate limits y no fijes en código un número de una guía antigua.

Reduce picos antes de pedir más cuota. Limita worker concurrency, usa una cola y reparte solicitudes. Si todos los workers despiertan tras la misma pausa, generan otra tormenta de reintentos.

503 UNAVAILABLE es indisponibilidad temporal o sobrecarga. No demuestra que el prompt sea incorrecto ni promete recuperación en un plazo fijo. Compara un probe mínimo en la misma cuenta/modelo/ruta, consulta el estado oficial y guarda request ID. Detén el automatismo al agotar el presupuesto de intentos o tiempo.

python
import random import time import requests TRANSIENT = {408, 429, 500, 502, 503, 504} def post_with_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("presupuesto total de retry agotado") 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"corrige la petición: {response.text[:500]}") if response.status_code not in TRANSIENT: 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("el siguiente retry excede el presupuesto") time.sleep(delay)

Usa una sola capa de retry. Si el SDK ya reintenta, otro bucle exterior puede multiplicar llamadas. Los valores del ejemplo son parámetros de aplicación ajustables, no una promesa del proveedor.

NO_IMAGE es un resultado de generación, no un error HTTP

No confundas el finishReason oficial con un mensaje genérico de la aplicación. En el contrato actual de Google, NO_IMAGE pertenece al candidate; un gateway puede mapearlo aparte a no_image. Conserva el body upstream y recórrelo así:

  1. Sin candidates: mira promptFeedback; un problema del prompt puede detener la respuesta antes de crear candidatos.
  2. finishReason=NO_IMAGE: guarda finishMessage, responseId y modelVersion; revisa la modalidad y ejecuta el probe mínimo por la misma ruta. No lo renombres como safety o 503 sin evidencia.
  3. IMAGE_SAFETY, IMAGE_PROHIBITED_CONTENT u otro motivo de policy: revisa una petición legítima o acepta el límite; retry no es una evasión.
  4. Solo partes de texto: lee el texto y verifica la modalidad. Una salida solo-texto no es la misma evidencia que NO_IMAGE.
  5. Hay inlineData pero la app dice que no hay imagen: revisa parser, MIME, base64 y escritura en almacenamiento.
python
def inspect_result(body): candidates = body.get("candidates") or [] if not candidates: return {"kind": "no_candidates", "feedback": body.get("promptFeedback")} candidate = candidates[0] finish_reason = candidate.get("finishReason") parts = ((candidate.get("content") or {}).get("parts") or []) images = [p["inlineData"] for p in parts if p.get("inlineData")] texts = [p["text"] for p in parts if p.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"), }

Para profundizar en seguridad y respuestas de solo texto, usa la guía de Gemini sin imagen e IMAGE_SAFETY. Si hay también errores de autenticación u otros HTTP, consulta la guía general de errores Gemini API.

Timeout: localiza la primera capa que cerró

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

Si el cliente cancela primero, quizá no haya body HTTP de Google, y un reverse proxy puede crear su propio 504. Registra un 504 del proveedor solo cuando aparece en la respuesta upstream. Lo contrario también importa: el timeout del cliente no demuestra que el servidor se detuvo. Antes de repetir una operación que crea una imagen o escritura downstream, consulta task o artifact por request/response ID para evitar duplicados. Mide inicio, primer byte, fin, deadline y actor que cerró cada tramo.

Compara por la misma cuenta/ruta/modelo el probe mínimo y la entrada original. Si el primero funciona y la petición compleja termina siempre en el deadline del cliente, ajusta la capa correcta o reduce la entrada. Si ambos fallan en la misma ventana 503/504, repetir la carga pesada aporta poca información.

Línea temporal de retry limitado, parada y escalado para Nano Banana API
Línea temporal de retry limitado, parada y escalado para Nano Banana API

Cuándo parar y qué enviar a soporte

Detén los reintentos ante 400 u otro client error conocido; si NO_IMAGE aún no está clasificado con probe mínimo y respuesta cruda; ante un finishReason de safety/policy; al agotar intentos/tiempo total; ante 5xx persistente para el mismo fingerprint; o cuando el llamador ya no pueda usar el resultado.

Entrega a soporte: intervalo UTC, provider/base URL, API version, endpoint/modelo, body de error anonimizado, request/upstream request ID, latencia de cada intento, timeout por capa, prompt mínimo, MIME de entrada, número de candidates, promptFeedback, finishReason, finishMessage, responseId, modelVersion, MIME de las partes y resultado de buscar artifacts tras el timeout.

La reparación solo está demostrada cuando la petición mínima devuelve una parte de imagen decodificable por la ruta real y la carga original termina después con concurrencia controlada. HTTP 200, task completed o la desaparición de NO_IMAGE en el adaptador no bastan por sí solos.