Cómo llamar a la API de Nano Banana Pro: recorrido completo
Esta es la integración entera, de principio a fin, para Nano Banana Pro en la API de E2X. Consiga una key, envíe un job, lea la response, espérela como es debido, gestione las formas en que puede fallar y ponga los bytes en algún sitio donde sigan estando mañana.
Ese último paso es el que pilla a la gente. Sáltese hasta ahí si ya está generando imágenes y solo quiere saber por qué las suyas desaparecieron.

Si Pro es el nivel correcto para su trabajo es otra pregunta y la respondimos aparte, en la página de precio y velocidad. Versión corta: $0.075 por request, unos 30 segundos, merece la pena si sus imágenes llevan texto legible o combinan referencias que hacen papeles distintos. Esta página da por hecho que ya lo decidió.
Qué necesita antes del primer request
Cuatro cosas, y tres de ellas son una línea cada una.
- Una API key. Créela desde su cuenta de E2X. Todo lo de abajo la envía como
Authorization: Bearer $E2X_API_KEY. Manténgala en el servidor. Una key en JavaScript de navegador es una key que ahora está gastando otra persona. - La URL base, que es
https://api.e2x.ai/v1para todos los modelos de el catálogo. - El slug correcto. La generación es
google/nano-banana-pro/text-to-image. La edición esgoogle/nano-banana-pro/edit-image. Esas cadenas van en el cuerpo del request tal cual, y no hay coincidencia aproximada. - Un prompt que merezca enviarse. Pro premia la especificidad más que los niveles flash, porque tiene más margen para actuar sobre ella. Tenemos una guía de prompting aparte si quiere profundizar más allá de «un gato, pero cinematográfico».
El contrato es asíncrono. Usted envía un job, recibe un ID, y la imagen llega después. No hay ningún endpoint síncrono que bloquee hasta que los píxeles estén listos, y dado que Pro tarda unos 30 segundos, tampoco lo querría.
Su primer request de text-to-image
curl -X POST https://api.e2x.ai/v1/jobs/submit \
-H "Authorization: Bearer $E2X_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/nano-banana-pro/text-to-image",
"input": {
"prompt": "A hand-lettered chalkboard menu behind a marble coffee counter, warm afternoon light from a window on the left, shallow depth of field",
"aspect_ratio": "16:9",
"resolution": "2k"
}
}'
Lo mismo en Python:
import os
import requests
BASE = "https://api.e2x.ai/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['E2X_API_KEY']}"}
def submit(prompt, aspect_ratio="16:9", resolution="2k"):
r = requests.post(
f"{BASE}/jobs/submit",
headers=HEADERS,
json={
"model": "google/nano-banana-pro/text-to-image",
"input": {
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"resolution": resolution,
},
},
timeout=30,
)
r.raise_for_status()
return r.json()["data"]["jobId"]
Los dos ejemplos fijan aspect_ratio y resolution a propósito, y merece la pena entender los dos valores por defecto antes de dejarlos caer.
aspect_ratio tiene por defecto 9:16. No 1:1. Si lo omite, cada imagen que genere es vertical, para siempre, y no lo notará hasta que un hueco ancho de su maquetación se vea mal. Este es el campo que hace tropezar a más integraciones de nuestra API. Fíjelo.
Los valores de resolution van en minúscula. 1k, 2k, 4k. 2K en mayúscula no es la misma cadena y la API se lo dirá.
Trate 1k como un valor muerto en este slug. Los dos primeros ajustes caen en un cargo plano —$0.075 cualquiera de ellos, comprobado el 26 de agosto de 2026—, así que el pequeño simplemente devuelve menos píxeles por la misma línea de factura. Solo 4k mueve la cifra: $0.15, con techo 4096×4096.

Qué vuelve
La llamada de submit devuelve al instante un sobre de job. El campo que necesita es data.jobId:
{
"success": true,
"data": {
"jobId": "job_8Kd2mQvXpL",
"status": "pending"
}
}
El estado se mueve pending → processing → completed, o aterriza en uno de los dos fallos terminales, failed y cancelled. Un job completado lleva su resultado en data.outputs[0].url, y uno fallido lleva un motivo en data.error.message.
Un detalle que hay que interiorizar antes de escribir nada de código de facturación: todos los valores monetarios que devuelve la API van en micro-céntimos. Un millón equivale a un dólar. Un request de Nano Banana Pro vuelve por tanto como 75000, no como 0.075. Divida entre 1.000.000 en la capa de presentación y en ningún otro sitio, y no guarde nunca el número dividido.
Polling sin machacar
La versión bruta funciona:
curl https://api.e2x.ai/v1/jobs/job_8Kd2mQvXpL \
-H "Authorization: Bearer $E2X_API_KEY"
Envuelva eso en un bucle con una espera fija de dos segundos y ya tiene una integración que funciona. Para Nano Banana Pro en concreto, un intervalo fijo es genuinamente aceptable: un request corre unos 30 segundos, así que hace unas quince llamadas de estado y para. Eso no es tráfico suficiente para molestar a nadie.
Un backoff sigue siendo mejor, por una razón que no tiene nada que ver con la buena educación. Los intervalos fijos esconden la varianza. Si un job tarda 90 segundos en lugar de 30, un bucle fijo sigue llamando al mismo ritmo y sus logs se ven idénticos a los de una ejecución sana. Un backoff que crece hace que un job lento se vea lento.
import time
TERMINAL = {"completed", "failed", "cancelled"}
def wait_for(job_id, timeout=300):
delay = 2.0
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
r = requests.get(f"{BASE}/jobs/{job_id}", headers=HEADERS, timeout=30)
r.raise_for_status()
data = r.json()["data"]
if data["status"] in TERMINAL:
return data
time.sleep(delay)
delay = min(delay * 1.4, 10.0)
raise TimeoutError(f"{job_id} still running after {timeout}s")
Dos cosas que hace ese bucle y que uno ingenuo normalmente no hace. Tiene una fecha límite dura, así que un job atascado lanza una excepción en lugar de girar hasta que maten el proceso. Y trata los tres estados terminales igual en la capa de transporte, devolviendo el payload y dejando que quien llama decida qué significa un fallo. La lógica de reintento va por encima de esta función, no dentro.

Webhooks, y por qué debería pasarse a ellos
Pase webhookUrl en el cuerpo del submit y le llamamos cuando el job alcanza un estado terminal. Sin bucle de polling ninguno.
curl -X POST https://api.e2x.ai/v1/jobs/submit \
-H "Authorization: Bearer $E2X_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/nano-banana-pro/text-to-image",
"input": {
"prompt": "An architectural model of a footbridge on a drafting table, north light",
"aspect_ratio": "3:2",
"resolution": "2k"
},
"webhookUrl": "https://your-app.example.com/hooks/e2x"
}'
El polling es el primer movimiento correcto porque lo puede probar desde una terminal en diez segundos. Es el estado estable equivocado, y la razón es aritmética. Con una imagen cada vez, el polling le cuesta un bucle. Con doscientas imágenes en un batch, el polling le cuesta doscientos bucles concurrentes, cada uno sosteniendo una conexión durante medio minuto, en un proceso que ya no se puede reiniciar sin perder el rastro de todos los jobs en vuelo.
Los webhooks hacen el trabajo reiniciable. El job ID entra en su base de datos cuando envía, el handler actualiza la fila cuando le llamamos, y un deploy en medio no cambia nada. Si está construyendo un pipeline de generación y no un script, esta es la versión que hay que construir, y encaja con los patrones de nuestra página sobre automatizar la generación de imágenes de principio a fin.
Dos notas operativas. Su endpoint tiene que ser alcanzable desde internet público, así que una URL de localhost nunca se disparará en silencio durante el desarrollo: use un túnel. Y trate el webhook como una notificación, no como la fuente de verdad: recupere el job por ID dentro del handler antes de actuar sobre él.
Editar una imagen en lugar de generarla
El mismo sobre, otro slug, un campo extra. El endpoint de edición admite image_urls junto al prompt y cuesta los mismos $0.075.
curl -X POST https://api.e2x.ai/v1/jobs/submit \
-H "Authorization: Bearer $E2X_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/nano-banana-pro/edit-image",
"input": {
"prompt": "Replace the background with a soft grey studio sweep, keep the product lighting exactly as it is",
"image_urls": ["https://your-cdn.example.com/source/bottle.jpg"],
"aspect_ratio": "1:1",
"resolution": "2k"
}
}'
Las URLs que pase tienen que ser accesibles públicamente. Una URL firmada de su propio almacenamiento funciona; una ruta de su portátil no.
Nano Banana Pro acepta hasta catorce imágenes de referencia, y a diferencia de los otros niveles las separa por rol: hasta 5 para la identidad de personaje, hasta 6 para la fidelidad de objeto, hasta 3 para el estilo. Esa separación por roles es la razón para estar en este nivel siquiera, y los nombres exactos de campo para las referencias con rol están documentados por modelo en el fichero de especificación legible por máquina, que es la autoridad si nuestra documentación y esta página llegaran a discrepar. Para el flujo de trabajo de consistencia de personaje en concreto, profundizamos en la guía de generación consistente.
Todos los parámetros y su valor por defecto
| Campo | Dónde va | Valores aceptados | Por defecto |
|---|---|---|---|
model | raíz del cuerpo | google/nano-banana-pro/text-to-image o google/nano-banana-pro/edit-image | obligatorio |
input.prompt | dentro de input | cadena | obligatorio |
input.aspect_ratio | dentro de input | 9:16, 16:9, 1:1, 2:3, 3:2, 21:9, 3:4, 4:3, 4:5, 5:4 | 9:16 |
input.resolution | dentro de input | 1k, 2k, 4k | fíjelo explícitamente |
input.image_urls | dentro de input, solo slug de edición | array de URLs accesibles públicamente | obligatorio al editar |
webhookUrl | raíz del cuerpo | un endpoint HTTPS público | ninguno, haga polling |
Las dos filas que le cuestan dinero a la gente son aspect_ratio y resolution. Todo lo demás se comporta como supondría.
Cuando el job no se completa
Tres superficies de fallo, y necesitan un manejo distinto.
La propia llamada de submit falla. Esto es un error HTTP antes de que exista ningún job: una key mala, un cuerpo malformado, un slug de modelo desconocido. No se encoló nada y no se cobró nada. Arregle el request; reintentarlo sin cambios fallará idéntico.
El job llega a failed. El job existió y el modelo no produjo una imagen. Lea data.error.message para el motivo, que suele ser un rechazo por política de contenido o un input malformado, como una URL inalcanzable en image_urls. Un reintento ciego de un rechazo por política falla igual; un reintento tras un error transitorio upstream normalmente sale bien. Registre el mensaje, no registre solo el estado.
El job llega a cancelled. Terminal, y no es un bug. Trátelo exactamente como failed a nivel de código: la fila está cerrada, no va a llegar ninguna salida.
Su bucle de espera agota el tiempo. No es un estado de job. Significa que el job sigue corriendo y que a usted se le acabó la paciencia. El job ID sigue siendo válido, así que anótelo y compruébelo más tarde en vez de reenviarlo, lo que le cobraría dos veces por la misma imagen.
def generate(prompt, **kw):
job_id = submit(prompt, **kw)
job = wait_for(job_id)
if job["status"] != "completed":
reason = (job.get("error") or {}).get("message", "no reason given")
raise RuntimeError(f"{job_id} ended as {job['status']}: {reason}")
return job["outputs"][0]["url"]
Descargue la salida antes de que caduque
Esta es la parte que entrega imágenes rotas una semana después del lanzamiento, así que se lleva su propia sección.
La URL de data.outputs[0].url es temporal. Es una URL de entrega, no alojamiento. Si escribe esa cadena en una columna de base de datos llamada image_url y la renderiza en una ficha de producto, la página funciona en staging, funciona en revisión, funciona el día del lanzamiento, y después se convierte calladamente en iconos de imagen rota en cuanto el objeto caduca.
El arreglo es un paso y no es opcional. Recupere los bytes, póngalos en su propio almacenamiento y guarde su URL.
import pathlib
def download(url, dest):
with requests.get(url, stream=True, timeout=120) as r:
r.raise_for_status()
pathlib.Path(dest).parent.mkdir(parents=True, exist_ok=True)
with open(dest, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 16):
f.write(chunk)
return dest
O, desde la shell:
curl -sL "$OUTPUT_URL" -o ./out/menu-board.jpg
Haga la descarga dentro de la misma unidad de trabajo que gestionó la finalización. No en un cron una hora después, no de forma perezosa en la primera visita a la página. La ventana es lo bastante generosa como para que se salga con la suya en pruebas y lo bastante estrecha como para que no se salga con la suya bajo carga.

Todo junto, como un solo script
Todo lo anterior, ensamblado. Fije E2X_API_KEY y córralo.
#!/usr/bin/env python3
"""Genera una imagen con Nano Banana Pro y la guarda en local."""
import os
import pathlib
import time
import requests
BASE = "https://api.e2x.ai/v1"
MODEL = "google/nano-banana-pro/text-to-image"
HEADERS = {"Authorization": f"Bearer {os.environ['E2X_API_KEY']}"}
TERMINAL = {"completed", "failed", "cancelled"}
def submit(prompt, aspect_ratio="16:9", resolution="2k"):
r = requests.post(
f"{BASE}/jobs/submit",
headers=HEADERS,
json={
"model": MODEL,
"input": {
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"resolution": resolution,
},
},
timeout=30,
)
r.raise_for_status()
return r.json()["data"]["jobId"]
def wait_for(job_id, timeout=300):
delay = 2.0
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
r = requests.get(f"{BASE}/jobs/{job_id}", headers=HEADERS, timeout=30)
r.raise_for_status()
data = r.json()["data"]
if data["status"] in TERMINAL:
return data
time.sleep(delay)
delay = min(delay * 1.4, 10.0)
raise TimeoutError(f"{job_id} still running after {timeout}s")
def download(url, dest):
with requests.get(url, stream=True, timeout=120) as r:
r.raise_for_status()
pathlib.Path(dest).parent.mkdir(parents=True, exist_ok=True)
with open(dest, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 16):
f.write(chunk)
return dest
def main():
job_id = submit(
"A hand-lettered chalkboard menu behind a marble coffee counter, "
"warm afternoon light from a window on the left, shallow depth of field"
)
print("submitted", job_id)
job = wait_for(job_id)
if job["status"] != "completed":
reason = (job.get("error") or {}).get("message", "no reason given")
raise SystemExit(f"{job_id} ended as {job['status']}: {reason}")
path = download(job["outputs"][0]["url"], "out/menu-board.jpg")
print("saved", path)
if __name__ == "__main__":
main()
Cambie el slug por google/nano-banana-pro/edit-image, añada image_urls y el mismo script edita en lugar de generar. Cámbielo por Nano Banana 2 o Nano Banana 2 Lite y sigue corriendo, porque el sobre es idéntico en todo lo de la categoría text-to-image y en todo lo de image-to-image. Solo difieren los campos de input, y la página de comparación de modelos cubre a cuál apuntarlo.
Una última cosa que no es código. Cada imagen de cada modelo de Google lleva un watermark SynthID y ningún proveedor puede desactivarlo. Resuélvalo con quien firme el contrato antes de construir encima.
Preguntas frecuentes
¿Cómo llamo a la API de Nano Banana Pro?
Mande un POST a https://api.e2x.ai/v1/jobs/submit con un bearer token, el slug de modelo google/nano-banana-pro/text-to-image y un objeto input que contenga su prompt. La response devuelve data.jobId. Haga polling de https://api.e2x.ai/v1/jobs/{id} o pase un webhookUrl en el cuerpo del submit, y después lea la imagen terminada de data.outputs[0].url.
¿Cuál es el aspect ratio por defecto de Nano Banana Pro?
9:16, que es vertical. Esto sorprende a casi todo el mundo, porque la mayoría de las APIs de imagen tienen por defecto el cuadrado. Si no fija aspect_ratio explícitamente, todas las imágenes salen verticales. El modelo acepta 9:16, 16:9, 1:1, 2:3, 3:2, 21:9, 3:4, 4:3, 4:5 y 5:4.
¿Por qué está rota la URL de mi imagen de Nano Banana Pro?
Porque la URL de salida que devolvemos es entrega temporal, no alojamiento permanente. Cualquier pipeline que guarde nuestra URL en una base de datos y la renderice después mostrará imágenes rotas en cuanto el objeto caduque. Descargue los bytes en el mismo paso que gestiona la finalización del job y guárdelos en su propio bucket.
¿Debería usar polling o webhooks con la API de E2X?
Haga polling mientras construye, porque lo puede probar desde una terminal. Pásese a webhooks para cualquier cosa que corra en producción. Un job de Nano Banana Pro tarda unos 30 segundos, así que un batch de doscientos significa doscientos bucles de polling concurrentes que pierden todos su estado en el siguiente deploy. Con webhookUrl, el job ID entra en su base de datos en el momento del envío y el handler lo recoge después.
¿Cómo edito una imagen existente con Nano Banana Pro?
Use el slug google/nano-banana-pro/edit-image y añada un array image_urls al objeto input junto a su prompt. Las URLs tienen que ser accesibles públicamente, así que una URL firmada de su almacenamiento funciona pero una ruta local no. Editar cuesta los mismos $0.075 que generar.
¿Qué resolución debería pedirle a Nano Banana Pro?
Envíe 2k. Los dos primeros ajustes comparten un cargo plano en este slug, así que 1k está estrictamente dominado: menos píxeles, línea de factura idéntica. Recurra a 4k solo cuando necesite de verdad el techo de 4096×4096, ya que dobla el cargo a $0.15. Los tres valores van en minúscula.
¿Por qué la API de E2X devuelve precios como 75000?
Todos los valores monetarios de la API se expresan en micro-céntimos, donde 1.000.000 equivale a un dólar estadounidense. Un 75000 en un job de Nano Banana Pro son $0.075. Guarde el entero en almacenamiento y divida solo en el punto en que lo lee una persona, para que el redondeo no se acumule nunca a lo largo de un periodo de facturación.