Error 429 y límites: cómo evitarlo al generar
Qué significa que la API no acepte una solicitud por superar un límite, cuántas solicitudes enviar a la vez, cómo reintentar con una pausa y por qué usar webhooks en lugar de consultar el estado con frecuencia.
Redacción de RendergridSobre la redacción
En resumen
Un 429 es el estado HTTP con el que la API avisa de que superaste un límite de tu cuenta y no atendió la solicitud. Los casos principales son tres: más de 120 solicitudes por minuto (ERR_RATE_LIMIT), más de 100 tareas en cola o en proceso (ERR_QUOTA_EXCEEDED) y una consulta de estado antes de 5 segundos (ERR_RATE_LIMIT). Para evitarlo, envía por debajo de esos topes, reintenta tras una pausa de al menos Retry-After segundos con la misma clave de idempotencia y usa webhooks en lugar de consultar sin parar.
¿Qué significa un error 429?
El 429 («Too Many Requests») no es una tarea fallida: una tarea aceptada que no llegó a producir imagen aparece en GET /creations/{id} con el estado failed. Un 429 responde a una solicitud que la API no atendió porque superaste un límite. Hay tres casos principales:
Demasiadas solicitudes por minuto. Son 120 por minuto, en ventanas de 60 segundos, para toda la cuenta. Al superarlo llega
429 ERR_RATE_LIMITcondetails.limit,details.window = "1m"ydetails.retry_after.Demasiadas tareas a la vez. La cuenta admite 100 tareas en cola o en proceso. Al superarlo llega
429 ERR_QUOTA_EXCEEDEDcondetails.scope = "account".Consultas de estado demasiado seguidas. Una misma clave puede consultar una misma tarea una vez cada 5 segundos como máximo. Una consulta anterior a ese plazo recibe
429 ERR_RATE_LIMITcondetails.min_interval_seconds.
Así llegan las respuestas de los dos primeros casos. Los valores de Retry-After son de ejemplo: léelos en cada respuesta.
Retry-After: 17 { "error": { "code": "ERR_RATE_LIMIT", "message": "rate limit exceeded", "details": { "limit": 120, "window": "1m", "retry_after": 17 } } }
Retry-After: 2 { "error": { "code": "ERR_QUOTA_EXCEEDED", "message": "concurrency limit reached", "details": { "limit": 100, "scope": "account" } } }
El texto de message está en inglés y puede cambiar: decide con el estado HTTP, code y details.
¿Cuántas solicitudes enviar a la vez?
Hay dos topes, ambos de la cuenta y no de cada clave: abrir más claves de API no los amplía.
El primero son las tareas simultáneas: hasta 100 en cola o en proceso, y cada una ocupa su lugar hasta que termina. La documentación recomienda mantener tu propia cola y lanzar las tareas nuevas a medida que se liberan lugares, en lugar de enviarlas todas de golpe. Fija un máximo menor que 100 y deja margen si más de un programa usa la cuenta: comparten el mismo cupo.
El segundo es la frecuencia: 120 solicitudes por minuto equivalen a una cada 0,5 segundos de media. Entran en ese recuento el inicio de generaciones, la carga de archivos, la lectura del catálogo y la del saldo; la consulta de estado se cuenta aparte.
Prompt: cubos de madera en fila
Cinco cubos de madera idénticos en una fila horizontal recta sobre gris claro. Mantén intervalos iguales y caras frontales alineadas. Cámara un poco elevada, luz suave desde la izquierda, todos los cubos completos y márgenes amplios. Sin texto, logotipos, personas ni marcas de agua.
Nano Banana 2.1
nano-banana-2.1 · 1K · 1:1 · $0,02
Nano Banana 2.1, 1K, 1:1, un intento. Cinco cubos de madera clara en una fila recta sobre un fondo gris claro, con la base de todos a la misma altura y los centros a la misma distancia a simple vista. Las sombras caen hacia la derecha de cada cubo, de modo que la luz suave llega desde la izquierda, como pedía el prompt. El resultado se aparta del prompt en los márgenes: arriba y abajo queda mucho espacio libre, pero los de los lados son estrechos. Los cubos tampoco son idénticos: los de los extremos parecen algo más anchos y la veta cambia de uno a otro. No hay texto, logotipos, personas ni marcas de agua.
¿Cómo reintentar después de un 429?
Espera y repite el mismo envío. La receta tiene cuatro pasos:
- Espera como mínimo
Retry-Aftersegundos. Las respuestas 429 traen ese encabezado. - Añade un pequeño retardo aleatorio, para que varios procesos no repitan a la vez.
- Limita el número de intentos y, si el último también recibe un 429, devuélvelo a tu código.
- Repite el mismo cuerpo con la misma clave de API y la misma clave de idempotencia (
X-Idempotency-Key): así no se crea una segunda tarea.
Si repites el envío mientras el primero aún se está aceptando, puede llegar un 429 con details.reason: idempotency_in_flight: espera y vuelve a enviar con la misma clave. Si el primero ya se aceptó, repetirlo con la misma clave devuelve la misma respuesta 202, con los mismos id, status, task_id y cost. Es la respuesta guardada, no el estado actual: puede decir queued aunque la imagen ya esté lista.
Una tarea que pasó de 202 a failed no se reinicia con la misma clave: un intento nuevo es una solicitud nueva, con una clave nueva y una operación nueva de pago; el cargo anterior se devuelve por sí solo.
El ejemplo hace como máximo cinco intentos, como el de la documentación de límites; elige tú el tope. Se detiene ante details.reason: burst_block, porque antes hay que corregir la causa de los errores 401 y 403.
# Hacen falta bash, curl y uuidgen. La clave de idempotencia se crea una sola vez por operación. IDEMPOTENCY_KEY="$(uuidgen)" || exit 1 # -i muestra el estado y los encabezados: en un 429 verás Retry-After curl -i --max-time 30 \ -X POST https://api.rendergrid.io/ api/ public/ v1/ images/ generate \ -H "Authorization: Bearer $RENDERGRID_API_KEY" \ -H "Content-Type: application/json" \ -H "X-Idempotency-Key: $IDEMPOTENCY_KEY" \ -d '{ "model": "nano-banana-2.1", "prompt": "Taza de cerámica sobre mármol, luz suave de ventana", "resolution": "1K", "aspect_ratio": "1:1" }' # con un 429, espera Retry-After segundos y repite este mismo comando con el mismo IDEMPOTENCY_KEY
import os import random import time import uuid import requests API = "https://api.rendergrid.io/ api/ public/ v1" HEADERS = {"Authorization": f"Bearer {os.environ['RENDERGRID_API_KEY']}"} def submit(payload, max_attempts=5): # una clave nueva por imagen; la misma en todos los reintentos de esa imagen headers = {**HEADERS, "X-Idempotency-Key": str(uuid.uuid4())} for attempt in range(max_attempts): response = requests.post( f"{API}/images/generate", headers=headers, json=payload, timeout=(10, 120) ) if response.status_code != 429: return response try: reason = response.json()["error"]["details"].get("reason") except (ValueError, KeyError, TypeError, AttributeError): reason = None if reason == "burst_block" or attempt + 1 == max_attempts: # con burst_block no reintentes: corrige antes la causa return response wait = response.headers.get("Retry-After", "") seconds = int(wait) if wait.isascii() and wait.isdigit() else 2 # como mínimo Retry-After, más un pequeño retardo aleatorio time.sleep(seconds + random.uniform(0, 1)) response = submit( { "model": "nano-banana-2.1", "prompt": "Taza de cerámica sobre mármol, luz suave de ventana", "resolution": "1K", "aspect_ratio": "1:1", } ) print(response.status_code, response.json())
// Node.js 22+, archivo .mjs import { randomUUID } from "node:crypto"; const API = "https://api.rendergrid.io/ api/ public/ v1"; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); async function submit(payload, maxAttempts = 5) { // una clave nueva por imagen; la misma en todos los reintentos de esa imagen const idempotencyKey = randomUUID(); for (let attempt = 0; attempt < maxAttempts; attempt++) { const response = await fetch(`${API}/images/generate`, { method: "POST", headers: { Authorization: `Bearer ${process.env.RENDERGRID_API_KEY}`, "Content-Type": "application/json", "X-Idempotency-Key": idempotencyKey, }, body: JSON.stringify(payload), signal: AbortSignal.timeout(120_000), }); if (response.status !== 429) return response; const body = await response .clone() .json() .catch(() => ({})); if (body?.error?.details?.reason === "burst_block" || attempt + 1 === maxAttempts) { // con burst_block no reintentes: corrige antes la causa return response; } const wait = response.headers.get("Retry-After"); const seconds = wait && /^[0-9]+$/.test(wait) ? Number(wait) : 2; // como mínimo Retry-After, más un pequeño retardo aleatorio await sleep((seconds + Math.random()) * 1000); } } const response = await submit({ model: "nano-banana-2.1", prompt: "Taza de cerámica sobre mármol, luz suave de ventana", resolution: "1K", aspect_ratio: "1:1", }); console.log(response.status, await response.json());
El ejemplo no espera el resultado: eso está en la documentación de tareas y estados.
¿Cada cuánto consultar el estado de una tarea?
Una misma clave puede consultar una misma tarea con GET /creations/{id} una vez cada 5 segundos como máximo. Ante un 429 con details.min_interval_seconds, espera el mayor entre ese valor y Retry-After: el encabezado a veces indica un intervalo menor.
Deja de consultar cuando el estado sea completed o failed, y limita el tiempo total de espera: si se agota, guarda el id y continúa más tarde, porque el tiempo de espera agotado de tu cliente no cancela la tarea.
La documentación no promete un plazo para el resultado: depende del modelo y de la carga. Con 100 tareas en proceso y una consulta cada 5 segundos por tarea, son 20 consultas por segundo.
Prompt: nueve tacitas en cuadrícula
Vista cenital de nueve tacitas blancas de cerámica sin asa, ordenadas en una cuadrícula de tres por tres. Espacios uniformes y bocas del mismo tamaño. Fondo beige mate, iluminación suave pareja y sombras finas; deja un marco vacío alrededor del conjunto. Sin texto, logotipos, personas ni marcas de agua.
Nano Banana 2.1
nano-banana-2.1 · 1K · 1:1 · $0,02
Nano Banana 2.1, 1K, 1:1, un intento. Nueve tacitas de cerámica blanca, con el borde blanco y el interior de un blanco algo grisáceo en la sombra, sin asa y vistas desde arriba, en una cuadrícula de tres filas de tres sobre un fondo beige mate. A simple vista las separaciones son iguales, las bocas tienen el mismo tamaño y queda un marco vacío parecido en los cuatro lados, como pedía el prompt. La iluminación se aparta un poco de «pareja»: cada taza proyecta una sombra suave hacia la derecha, así que la luz llega algo desde la izquierda. No hay texto, logotipos, personas ni marcas de agua.
¿Cuándo usar webhooks en lugar de consultar?
Con muchas tareas, las consultas se multiplican. Con un webhook no consultas: indica webhook_url en cada solicitud de generación y la API envía un POST firmado a esa dirección cuando la tarea pasa a completed o failed.
La dirección. HTTPS pública, de hasta 2 048 caracteres; si es incorrecta o está prohibida, la solicitud devuelve
400antes del cargo.La firma.
X-Webhook-Signature-256es un HMAC-SHA256 deX-Timestamp, un punto y el cuerpo original. Compruébala antes de actuar.La respuesta. Responde con un
2xxen menos de 10 segundos.Los reintentos. Sin un
2xx, repetimos la entrega hasta cinco intentos en total, con esperas de 60, 120, 240 y 480 segundos.
La entrega no está garantizada al cien por cien: conserva la consulta de estado como respaldo. Y un webhook no sustituye al tope de 100 tareas simultáneas. Más detalles en la documentación de webhooks y en la guía sobre lotes, webhooks e idempotencia.
¿Qué recibes cuando la tarea termina?
Una tarea completed trae result_urls, los enlaces a los archivos. Descarga la imagen a tu almacenamiento enseguida: los archivos se conservan un día y consultar la tarea no prolonga ese plazo. Una tarea failed trae failure con type, code y retryable, y el importe cargado vuelve al saldo por sí solo.
Prompt: cinco frascos en arco
Cinco frascos crema iguales, cerrados, forman un arco suave sobre fondo arena. Vista algo elevada, separaciones uniformes y tapas redondas lisas. La luz suave de la derecha genera sombras individuales. No cortes los frascos y deja vacío el centro del arco. Sin texto, logotipos, personas ni marcas de agua.
Nano Banana 2.1
nano-banana-2.1 · 1K · 1:1 · $0,02
Nano Banana 2.1, 1K, 1:1, un intento. Cinco frascos cerrados de color blanco roto y tono cálido, con tapa redonda lisa, forman un arco abierto hacia abajo sobre arena con ondulaciones; el centro del arco queda vacío y los cinco frascos se ven completos. Se aparta del prompt en la luz y en la vista. Pedía luz suave desde la derecha, pero las sombras de los frascos caen hacia la derecha, es decir, la luz llega desde la izquierda, y esas sombras tienen el borde definido, no suave. Tampoco son del todo «individuales» como pedía el prompt: las de los frascos vecinos se tocan junto a las bases, sobre todo en la parte alta del arco. La vista es más alta que la «algo elevada» del prompt: la arena llena todo el encuadre y las tapas se ven desde arriba. Los frascos de los extremos quedan cerca del borde de la imagen, y a simple vista las separaciones entre frascos son parecidas. No hay texto, logotipos, personas ni marcas de agua.
Las tres imágenes de este artículo son resultados de tareas completadas con Nano Banana 2.1, un intento cada una. No son pantallas de error ni miden velocidad o carga.
¿Qué no cubre este artículo?
- Son los valores predeterminados. El límite de una cuenta puede ser otro: si te queda corto, escribe a soporte con la cuenta y el volumen que necesitas.
- No todos los 429 son de volumen. Una cuenta que se registró sola y no ha recargado el saldo puede usar 100 imágenes de referencia por día: recarga el saldo o espera al día siguiente. Y 30 errores
401o 30 errores403en un minuto bloquean la IP hasta 120 segundos (burst_block): corrige antes la causa. - Un 429 no es un 402. Sin saldo suficiente, o con el límite mensual agotado, la respuesta es
402 ERR_INSUFFICIENT_BALANCE. - Un rechazo por las reglas de contenido no se arregla con una pausa. Llega como una tarea
failedconfailure.type: declined: cambia la solicitud.
Antes de enviar en volumen
Prueba tu prompt en Nano Banana 2.1
Ajusta el prompt en la zona de pruebas del modelo y pásalo a la API cuando el resultado te sirva.
Probar Nano Banana 2.1Preguntas frecuentes
¿Qué significa un error 429 en la API de Rendergrid?
Retry-After y repite con la misma clave de idempotencia.¿Más claves de API suben los límites?
¿Cada cuánto puedo consultar una tarea?
completed o failed. Con un webhook no hace falta consultar.¿Repetir un envío con la misma clave de idempotencia crea otra imagen?
202. Para una imagen nueva, o para un intento nuevo después de un failed, usa una clave nueva.Fuentes
- LímitesLos valores predeterminados, las respuestas 429 y el ejemplo de reintentos.
- Códigos de errorEl formato del error,
Retry-Aftery las tareas con estadofailed. - Tareas y estadosLa frecuencia de las consultas de estado y los archivos del resultado.
- WebhooksLa firma, la respuesta y los reintentos de las entregas.
- Reintentos sin doble cargoLa clave de idempotencia y lo que devuelve un reintento.





