Come chiamare l'API Nano Banana Pro: guida completa
Questa è tutta l'integrazione, dall'inizio alla fine, per Nano Banana Pro sull'API E2X. Prenda una key, invii un job, legga la response, aspetti come si deve, gestisca i modi in cui può fallire, e metta i byte da qualche parte che ci sarà ancora domani.
È l'ultimo passo quello che frega la gente. Ci salti direttamente se sta già generando immagini e vuole solo sapere perché le Sue sono sparite.

Se Pro sia il tier giusto per il Suo lavoro è un'altra domanda e le abbiamo risposto a parte, nell'articolo su prezzo e velocità. Versione breve: $0.075 a request, una trentina di secondi, ne vale la pena se le Sue immagini portano testo leggibile o combinano reference che giocano ruoli diversi. Questa pagina dà per scontato che abbia già deciso.
Cosa Le serve prima della prima request
Quattro cose, e tre di queste sono una riga ciascuna.
- Una API key. La crei dal Suo account E2X. Tutto quello che segue la invia come
Authorization: Bearer $E2X_API_KEY. La tenga lato server. Una key dentro il JavaScript del browser è una key che adesso sta spendendo qualcun altro. - La base URL, che è
https://api.e2x.ai/v1per ogni modello del catalogo. - Lo slug giusto. La generazione è
google/nano-banana-pro/text-to-image. L'editing ègoogle/nano-banana-pro/edit-image. Quelle stringhe vanno nel corpo della request alla lettera, e non c'è alcun matching approssimativo. - Un prompt che valga la pena inviare. Pro premia la precisione più dei tier flash, perché ha più spazio per agirci sopra. Abbiamo una guida al prompting a parte se vuole andare più a fondo di "un gatto, ma cinematografico".
Il contratto è asincrono. Invia un job, riceve un ID, e l'immagine arriva dopo. Non esiste un endpoint sincrono che blocchi finché i pixel non sono pronti e, dato che Pro ci mette una trentina di secondi, non lo vorrebbe.
La Sua prima request 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": "Un menu su lavagna scritto a mano dietro un bancone da caffè in marmo, calda luce pomeridiana da una finestra a sinistra, profondità di campo ridotta",
"aspect_ratio": "16:9",
"resolution": "2k"
}
}'
La stessa cosa in 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"]
Entrambi gli esempi fissano aspect_ratio e resolution di proposito, e vale la pena capire entrambi i default prima di lasciarli cadere.
aspect_ratio ha come default 9:16. Non 1:1. Se lo omette, ogni immagine che genera è verticale, per sempre, e non se ne accorgerà finché uno slot largo nel Suo layout non sembrerà sbagliato. È il campo che manda fuori strada più integrazioni di qualunque altro sulla nostra API. Lo fissi.
I valori di resolution sono minuscoli. 1k, 2k, 4k. 2K maiuscolo non è la stessa stringa e l'API glielo dirà.
Tratti 1k come un valore morto su questo slug. Le prime due impostazioni stanno su un unico addebito piatto — $0.075 per entrambe, verificato il 26 agosto 2026 — quindi la più piccola restituisce solo meno pixel a parità di riga in fattura. Solo 4k sposta il numero: $0.15, tetto 4096×4096.

Cosa torna indietro
La chiamata di submit torna subito con una busta di job. Il campo che Le serve è data.jobId:
{
"success": true,
"data": {
"jobId": "job_8Kd2mQvXpL",
"status": "pending"
}
}
Lo status si muove pending → processing → completed, oppure atterra su uno dei due fallimenti terminali, failed e cancelled. Un job completato porta il proprio risultato in data.outputs[0].url, e uno fallito porta una ragione in data.error.message.
Un dettaglio da interiorizzare prima di scrivere qualsiasi codice di fatturazione: ogni valore monetario che l'API restituisce è in micro-centesimi. Un milione equivale a un dollaro. Una request Nano Banana Pro torna quindi come 75000, non come 0.075. Divida per 1.000.000 al livello di visualizzazione e da nessun'altra parte, e non salvi mai il numero diviso.
Fare polling senza martellare
La versione grezza funziona:
curl https://api.e2x.ai/v1/jobs/job_8Kd2mQvXpL \
-H "Authorization: Bearer $E2X_API_KEY"
Avvolga quella in un ciclo con una pausa fissa di due secondi e ha un'integrazione funzionante. Per Nano Banana Pro in particolare un intervallo fisso è davvero accettabile — una request gira una trentina di secondi, quindi fa grossomodo quindici chiamate di status e si ferma. Non è traffico sufficiente a disturbare nessuno.
Un backoff resta comunque meglio, per una ragione che non ha niente a che vedere con l'educazione. Gli intervalli fissi nascondono la varianza. Se un job ci mette 90 secondi invece di 30, un ciclo fisso continua a chiamare allo stesso ritmo e i Suoi log sembrano identici a quelli di un run sano. Un backoff che cresce rende visibilmente lento un job 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")
Due cose che quel ciclo fa e che uno ingenuo di solito non fa. Ha una scadenza secca, quindi un job bloccato solleva un'eccezione invece di girare finché il processo non viene ucciso. E tratta tutti e tre gli stati terminali allo stesso modo a livello di trasporto, restituendo il payload e lasciando decidere al chiamante che cosa significhi un fallimento. La logica di retry sta sopra questa funzione, non dentro.

I webhook, e perché dovrebbe passarci
Passi webhookUrl nel corpo del submit e La chiamiamo noi quando il job raggiunge uno stato terminale. Nessun ciclo di polling.
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": "Un modello architettonico di una passerella pedonale su un tavolo da disegno, luce da nord",
"aspect_ratio": "3:2",
"resolution": "2k"
},
"webhookUrl": "https://your-app.example.com/hooks/e2x"
}'
Il polling è la prima mossa giusta perché lo può testare da un terminale in dieci secondi. È lo stato stabile sbagliato, e la ragione è aritmetica. Un'immagine alla volta, il polling Le costa un ciclo. Duecento immagini in un batch e il polling Le costa duecento cicli concorrenti, ciascuno che tiene una connessione per mezzo minuto, in un processo che adesso non si può riavviare senza perdere traccia di ogni job in volo.
I webhook rendono il lavoro riavviabile. Il job ID va nel Suo database quando invia, l'handler aggiorna la riga quando chiamiamo, e un deploy nel mezzo non cambia niente. Se sta costruendo una pipeline di generazione invece di uno script, è questa la versione da costruire, e si sposa con gli schemi del nostro articolo su automatizzare la generazione di immagini da capo a fondo.
Due note operative. Il Suo endpoint deve essere raggiungibile da internet pubblica, quindi un URL localhost non scatterà mai, in silenzio, durante lo sviluppo — usi un tunnel. E tratti il webhook come una notifica, non come la fonte di verità: recuperi il job per ID dentro l'handler prima di agire.
Editare un'immagine invece di generarla
Stessa busta, altro slug, un campo in più. L'endpoint di editing prende image_urls accanto al prompt e costa gli stessi $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": "Sostituisci lo sfondo con un fondale da studio grigio morbido, mantieni la luce sul prodotto esattamente come sta adesso",
"image_urls": ["https://your-cdn.example.com/source/bottle.jpg"],
"aspect_ratio": "1:1",
"resolution": "2k"
}
}'
Gli URL che passa devono essere recuperabili pubblicamente. Un URL firmato dal Suo storage funziona; un percorso sul Suo portatile no.
Nano Banana Pro accetta fino a quattordici reference image e, a differenza degli altri tier, le separa per ruolo — fino a 5 per l'identità di un personaggio, fino a 6 per la fedeltà di un oggetto, fino a 3 per lo stile. Quella divisione per ruolo è la ragione stessa per stare su questo tier, e i nomi esatti dei campi per le reference legate a un ruolo sono documentati modello per modello nel file di specifica leggibile dalla macchina, che fa fede se la nostra documentazione e questo articolo dovessero mai divergere. Sul workflow di coerenza di personaggio nello specifico andiamo più a fondo nella guida alla generazione coerente.
Ogni parametro, con il suo default
| Campo | Dove va | Valori accettati | Default |
|---|---|---|---|
model | radice del body | google/nano-banana-pro/text-to-image o google/nano-banana-pro/edit-image | obbligatorio |
input.prompt | dentro input | stringa | obbligatorio |
input.aspect_ratio | dentro 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 input | 1k, 2k, 4k | lo fissi esplicitamente |
input.image_urls | dentro input, solo slug di editing | array di URL raggiungibili pubblicamente | obbligatorio in editing |
webhookUrl | radice del body | un endpoint HTTPS pubblico | nessuno, faccia polling |
Le due righe che costano soldi alla gente sono aspect_ratio e resolution. Tutto il resto si comporta come immaginerebbe.
Quando il job non si completa
Tre superfici di fallimento, e vogliono gestioni diverse.
Fallisce la chiamata di submit stessa. È un errore HTTP prima che esista qualsiasi job — key sbagliata, body malformato, slug di modello sconosciuto. Non è stato messo in coda niente e non è stato addebitato niente. Sistemi la request; ripeterla identica fallirà allo stesso modo.
Il job arriva a failed. Il job è esistito e il modello non ha prodotto un'immagine. Legga data.error.message per la ragione, che di solito è un rifiuto per policy sui contenuti o un input malformato, come un URL non raggiungibile in image_urls. Un retry cieco di un rifiuto per policy fallisce allo stesso modo; un retry dopo un errore transitorio a monte di solito riesce. Logghi il messaggio, non solo lo status.
Il job arriva a cancelled. Terminale, e non è un bug. Lo tratti esattamente come failed a livello di codice — la riga è chiusa, non arriverà alcun output.
Il Suo ciclo di attesa va in timeout. Non è uno stato del job. Significa che il job sta ancora girando e la Sua pazienza è finita. Il job ID è ancora valido, quindi lo registri e ricontrolli più tardi invece di reinviare, cosa che Le addebiterebbe due volte la stessa immagine.
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"]
Scarichi l'output prima che scada
È la parte che manda in produzione immagini rotte una settimana dopo il lancio, quindi si prende una sezione tutta sua.
L'URL in data.outputs[0].url è temporaneo. È un URL di consegna, non hosting. Se scrive quella stringa in una colonna di database chiamata image_url e la renderizza su una pagina prodotto, la pagina funziona in staging, funziona in revisione, funziona il giorno del lancio, e poi si trasforma in silenzio in icone di immagini rotte quando l'oggetto invecchia.
La soluzione è un passaggio e non è opzionale. Recuperi i byte, li metta nel Suo storage, salvi il Suo 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
Oppure, dalla shell:
curl -sL "$OUTPUT_URL" -o ./out/menu-board.jpg
Faccia il download dentro la stessa unità di lavoro che ha gestito il completamento. Non su un cron un'ora dopo, non in modo pigro alla prima visualizzazione della pagina. La finestra è abbastanza generosa da farLa cavare con un ritardo in fase di test, e abbastanza stretta da non farLa cavare sotto carico.

Tutto quanto, in un unico script
Tutto quello sopra, assemblato. Imposti E2X_API_KEY e lo esegua.
#!/usr/bin/env python3
"""Genera un'immagine con Nano Banana Pro e la salva in locale."""
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(
"Un menu su lavagna scritto a mano dietro un bancone da caffè in marmo, "
"calda luce pomeridiana da una finestra a sinistra, profondità di campo ridotta"
)
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()
Sostituisca lo slug con google/nano-banana-pro/edit-image, aggiunga image_urls e lo stesso script edita invece di generare. Lo sostituisca con Nano Banana 2 o Nano Banana 2 Lite e gira lo stesso, perché la busta è identica su tutto ciò che sta nella categoria text-to-image e su tutto ciò che sta in image-to-image. Cambiano solo i campi di input, e l'articolo di confronto fra i modelli racconta su quale puntarlo.
Un'ultima cosa che non è codice. Ogni immagine di ogni modello Google porta un watermark SynthID e nessun provider può disattivarlo. Lo chiarisca con chi firma il contratto prima di costruirci sopra.
Domande frequenti
Come si chiama l'API di Nano Banana Pro?
Mandi una POST a https://api.e2x.ai/v1/jobs/submit con un bearer token, lo slug di modello google/nano-banana-pro/text-to-image e un oggetto input che contenga il Suo prompt. La response restituisce data.jobId. Faccia polling su https://api.e2x.ai/v1/jobs/{id} oppure passi un webhookUrl nel corpo del submit, poi legga l'immagine finita da data.outputs[0].url.
Qual è l'aspect ratio di default di Nano Banana Pro?
9:16, cioè verticale. Sorprende quasi tutti, perché quasi tutte le API immagine partono dal quadrato. Se non imposta aspect_ratio esplicitamente, ogni immagine esce verticale. Il modello accetta 9:16, 16:9, 1:1, 2:3, 3:2, 21:9, 3:4, 4:3, 4:5 e 5:4.
Perché l'URL della mia immagine Nano Banana Pro è rotto?
Perché l'URL di output che restituiamo è consegna temporanea, non hosting permanente. Qualsiasi pipeline che salvi il nostro URL in un database e lo renderizzi più tardi mostrerà immagini rotte quando l'oggetto scade. Scarichi i byte nello stesso passaggio che gestisce il completamento del job e li salvi nel Suo bucket.
Meglio polling o webhook con l'API E2X?
Faccia polling mentre costruisce, perché lo può testare da un terminale. Passi ai webhook per qualsiasi cosa giri in produzione. Un job Nano Banana Pro ci mette una trentina di secondi, quindi un batch da duecento significa duecento cicli di polling concorrenti che perdono tutti il proprio stato al deploy successivo. Con webhookUrl il job ID entra nel Suo database al momento del submit e l'handler lo riprende più tardi.
Come edito un'immagine esistente con Nano Banana Pro?
Usi lo slug google/nano-banana-pro/edit-image e aggiunga un array image_urls all'oggetto input accanto al Suo prompt. Gli URL devono essere recuperabili pubblicamente, quindi un URL firmato dal Suo storage funziona ma un percorso locale no. L'editing costa gli stessi $0.075 della generazione.
Che risoluzione dovrei chiedere a Nano Banana Pro?
Mandi 2k. Le prime due impostazioni condividono un unico addebito piatto su questo slug, quindi 1k è strettamente dominata — meno pixel, riga in fattura identica. Ricorra a 4k solo quando Le serve davvero il tetto di 4096×4096, dato che raddoppia l'addebito a $0.15. Tutti e tre i valori sono minuscoli.
Perché l'API E2X restituisce prezzi come 75000?
Ogni valore monetario nell'API è espresso in micro-centesimi, dove 1.000.000 equivale a un dollaro statunitense. Un 75000 su un job Nano Banana Pro sono $0.075. Tenga l'intero nello storage e divida solo nel punto in cui lo legge un essere umano, così l'arrotondamento non si accumula mai lungo un periodo di fatturazione.