Comment appeler l'API Nano Banana Pro : le guide complet
Voici l'intégration entière, du début à la fin, pour Nano Banana Pro sur l'API E2X. Prenez une clé, soumettez un job, lisez la réponse, attendez-la correctement, gérez les façons dont ça peut échouer, et posez les octets quelque part où ils seront encore là demain.
C'est cette dernière étape qui attrape tout le monde. Sautez-y directement si vous générez déjà des images et voulez juste savoir pourquoi les vôtres ont disparu.

Savoir si Pro est le bon palier pour votre travail est une autre question, et nous y avons répondu à part, dans l'article sur le prix et la vitesse. Version courte : $0.075 la requête, une trentaine de secondes, rentable si vos images portent du texte lisible ou combinent des références qui jouent des rôles différents. Cette page suppose que c'est tranché.
Ce qu'il vous faut avant la première requête
Quatre choses, et trois d'entre elles tiennent en une ligne.
- Une clé d'API. Créez-en une depuis votre compte E2X. Tout ce qui suit l'envoie sous la forme
Authorization: Bearer $E2X_API_KEY. Gardez-la côté serveur. Une clé dans du JavaScript de navigateur est une clé que quelqu'un d'autre est en train de dépenser. - L'URL de base, qui est
https://api.e2x.ai/v1pour tous les modèles du catalogue. - Le bon slug. La génération, c'est
google/nano-banana-pro/text-to-image. L'édition, c'estgoogle/nano-banana-pro/edit-image. Ces chaînes vont dans le corps de la requête telles quelles, et il n'y a aucune correspondance approximative dessus. - Un prompt qui mérite d'être envoyé. Pro récompense la précision plus que les paliers flash, parce qu'il a plus de marge pour en tenir compte. Nous avons un guide de prompting à part si vous voulez aller plus loin que « un chat, mais cinématographique ».
Le contrat est asynchrone. Vous soumettez un job, vous récupérez un ID, et l'image arrive plus tard. Il n'existe pas d'endpoint synchrone qui bloque jusqu'à ce que les pixels soient prêts, et vu que Pro prend une trentaine de secondes, vous n'en voudriez pas.
Votre première requête 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": "Une ardoise de menu calligraphiée à la main derrière un comptoir de café en marbre, lumière chaude de fin de journée venant par une fenêtre à gauche, faible profondeur de champ",
"aspect_ratio": "16:9",
"resolution": "2k"
}
}'
La même chose 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"]
Les deux exemples épinglent aspect_ratio et resolution volontairement, et les deux valeurs par défaut méritent d'être comprises avant que vous ne les abandonniez.
aspect_ratio vaut 9:16 par défaut. Pas 1:1. Si vous l'omettez, chaque image que vous générez est en portrait, à perpétuité, et vous ne le remarquerez pas avant qu'un emplacement large de votre mise en page ait l'air faux. C'est le champ qui fait trébucher le plus d'intégrations sur notre API. Épinglez-le.
Les valeurs de resolution sont en minuscules. 1k, 2k, 4k. 2K en majuscule n'est pas la même chaîne et l'API vous le fera savoir.
Considérez 1k comme une valeur morte sur ce slug. Les deux premiers réglages tombent sur une même facturation plate — $0.075 pour l'un comme pour l'autre, vérifié le 26 août 2026 — donc le plus petit ne fait que renvoyer moins de pixels pour la même ligne de facture. Seul 4k déplace le chiffre : $0.15, plafond 4096×4096.

Ce qui revient
L'appel submit renvoie immédiatement une enveloppe de job. Le champ dont vous avez besoin est data.jobId :
{
"success": true,
"data": {
"jobId": "job_8Kd2mQvXpL",
"status": "pending"
}
}
Le statut passe de pending à processing puis completed, ou atterrit sur l'un des deux échecs terminaux, failed et cancelled. Un job terminé porte son résultat dans data.outputs[0].url, et un job en échec porte une raison dans data.error.message.
Un détail à intégrer avant d'écrire le moindre code de facturation : toutes les valeurs monétaires que renvoie l'API sont en micro-cents. Un million égale un dollar. Une requête Nano Banana Pro revient donc en 75000, pas en 0.075. Divisez par 1 000 000 à la couche d'affichage et nulle part ailleurs, et ne stockez jamais le nombre divisé.
Faire du polling sans matraquer
La version brutale fonctionne :
curl https://api.e2x.ai/v1/jobs/job_8Kd2mQvXpL \
-H "Authorization: Bearer $E2X_API_KEY"
Enveloppez ça dans une boucle avec une pause fixe de deux secondes et vous avez une intégration qui marche. Pour Nano Banana Pro en particulier, un intervalle fixe est sincèrement acceptable — une requête tourne une trentaine de secondes, donc vous faites une quinzaine d'appels de statut et vous vous arrêtez. Ça ne dérange personne.
Un backoff reste préférable, pour une raison qui n'a rien à voir avec la politesse. Les intervalles fixes masquent la variance. Si un job prend 90 secondes au lieu de 30, une boucle fixe continue d'appeler au même rythme et vos logs ressemblent trait pour trait à ceux d'un run sain. Un backoff qui grandit rend un job lent visiblement lent.
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} tourne toujours après {timeout}s")
Deux choses que cette boucle fait et qu'une boucle naïve ne fait généralement pas. Elle a une échéance dure, donc un job bloqué lève une exception au lieu de tourner jusqu'à ce qu'on tue le processus. Et elle traite les trois états terminaux de la même façon au niveau transport, en renvoyant le payload et en laissant l'appelant décider de ce qu'un échec signifie. La logique de retry appartient au-dessus de cette fonction, pas dedans.

Les webhooks, et pourquoi il faut y passer
Passez webhookUrl dans le corps du submit et nous vous appelons quand le job atteint un état terminal. Plus de boucle de polling du tout.
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": "Une maquette architecturale de passerelle piétonne sur une table à dessin, lumière du nord",
"aspect_ratio": "3:2",
"resolution": "2k"
},
"webhookUrl": "https://your-app.example.com/hooks/e2x"
}'
Le polling est le bon premier geste parce que vous le testez depuis un terminal en dix secondes. C'est le mauvais régime de croisière, et la raison est arithmétique. Une image à la fois, le polling vous coûte une boucle. Deux cents images en batch et le polling vous coûte deux cents boucles concurrentes, chacune tenant une connexion une demi-minute, dans un processus qu'on ne peut désormais plus redémarrer sans perdre la trace de tous les jobs en vol.
Les webhooks rendent le travail redémarrable. Le job ID part dans votre base au moment du submit, le handler met la ligne à jour quand nous appelons, et un deploy au milieu ne change rien. Si vous construisez un pipeline de génération plutôt qu'un script, c'est cette version-là qu'il faut construire, et elle se marie avec les patterns de notre article sur l'automatisation de la génération d'images de bout en bout.
Deux notes opérationnelles. Votre endpoint doit être joignable depuis l'internet public : une URL en localhost ne se déclenchera jamais, silencieusement, pendant le développement — passez par un tunnel. Et traitez le webhook comme une notification, pas comme la source de vérité : allez chercher le job par son ID dans le handler avant d'agir dessus.
Éditer une image au lieu d'en générer une
Même enveloppe, autre slug, un champ de plus. L'endpoint edit prend image_urls à côté du prompt et coûte les mêmes $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": "Remplacer le fond par un fond de studio gris doux sans raccord, garder exactement le même éclairage produit",
"image_urls": ["https://your-cdn.example.com/source/bottle.jpg"],
"aspect_ratio": "1:1",
"resolution": "2k"
}
}'
Les URL que vous passez doivent être publiquement récupérables. Une URL signée depuis votre propre stockage fonctionne ; un chemin sur votre portable, non.
Nano Banana Pro accepte jusqu'à quatorze images de référence et, contrairement aux autres paliers, il les sépare par rôle — jusqu'à 5 pour l'identité d'un personnage, jusqu'à 6 pour la fidélité d'un objet, jusqu'à 3 pour le style. Cette répartition par rôle est la raison même d'être sur ce palier, et les noms de champs exacts pour les références rattachées à un rôle sont documentés modèle par modèle dans le fichier de spec lisible par machine, qui fait autorité si notre documentation et cet article venaient à diverger. Pour le workflow de cohérence de personnage en particulier, nous creusons dans le guide de génération cohérente.
Tous les paramètres, et leur valeur par défaut
| Champ | Où il va | Valeurs acceptées | Défaut |
|---|---|---|---|
model | racine du body | google/nano-banana-pro/text-to-image ou google/nano-banana-pro/edit-image | obligatoire |
input.prompt | dans input | chaîne | obligatoire |
input.aspect_ratio | dans 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 | dans input | 1k, 2k, 4k | épinglez-la explicitement |
input.image_urls | dans input, slug edit uniquement | tableau d'URL publiquement joignables | obligatoire en édition |
webhookUrl | racine du body | un endpoint HTTPS public | aucun, faites du polling |
Les deux lignes qui coûtent de l'argent sont aspect_ratio et resolution. Tout le reste se comporte comme vous le devineriez.
Quand le job ne se termine pas
Trois surfaces de défaillance, et elles réclament des traitements différents.
L'appel submit lui-même échoue. C'est une erreur HTTP avant qu'aucun job n'existe — mauvaise clé, corps malformé, slug de modèle inconnu. Rien n'a été mis en file et rien n'a été facturé. Corrigez la requête ; la rejouer telle quelle échouera de façon identique.
Le job atteint failed. Le job a existé et le modèle n'a pas produit d'image. Lisez data.error.message pour la raison, généralement un refus de politique de contenu ou une entrée malformée comme une URL injoignable dans image_urls. Un retry aveugle sur un refus de politique échoue pareil ; un retry après une erreur upstream transitoire réussit généralement. Journalisez le message, pas seulement le statut.
Le job atteint cancelled. Terminal, et ce n'est pas un bug. Traitez-le exactement comme failed au niveau du code — la ligne est close, aucune sortie n'arrivera.
Votre boucle d'attente expire. Ce n'est pas un état de job. Ça veut dire que le job tourne toujours et que votre patience s'est épuisée. Le job ID reste valide : notez-le et revérifiez plus tard plutôt que de resoumettre, ce qui vous facturerait deux fois la même image.
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", "aucune raison fournie")
raise RuntimeError(f"{job_id} terminé en {job['status']} : {reason}")
return job["outputs"][0]["url"]
Téléchargez la sortie avant qu'elle n'expire
C'est la partie qui livre des images cassées une semaine après le lancement, alors elle a droit à sa propre section.
L'URL dans data.outputs[0].url est temporaire. C'est une URL de livraison, pas de l'hébergement. Si vous écrivez cette chaîne dans une colonne de base appelée image_url et que vous l'affichez sur une fiche produit, la page marche en staging, marche en revue, marche le jour du lancement, puis se transforme discrètement en icônes d'image cassée dès que l'objet vieillit.
Le correctif tient en une étape et il n'est pas optionnel. Récupérez les octets, posez-les dans votre propre stockage, stockez votre 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
Ou, depuis le shell :
curl -sL "$OUTPUT_URL" -o ./out/menu-board.jpg
Faites le téléchargement dans la même unité de travail que celle qui a traité la complétion. Pas sur un cron une heure plus tard, pas paresseusement au premier affichage de page. La fenêtre est assez large pour que vous vous en tiriez avec un délai en test, et assez étroite pour que vous ne vous en tiriez pas sous charge.

Le tout, en un seul script
Tout ce qui précède, assemblé. Réglez E2X_API_KEY et lancez-le.
#!/usr/bin/env python3
"""Génère une image avec Nano Banana Pro et la stocke 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} tourne toujours après {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(
"Une ardoise de menu calligraphiée à la main derrière un comptoir de café "
"en marbre, lumière chaude de fin de journée venant par une fenêtre à "
"gauche, faible profondeur de champ"
)
print("soumis", job_id)
job = wait_for(job_id)
if job["status"] != "completed":
reason = (job.get("error") or {}).get("message", "aucune raison fournie")
raise SystemExit(f"{job_id} terminé en {job['status']} : {reason}")
path = download(job["outputs"][0]["url"], "out/menu-board.jpg")
print("enregistré", path)
if __name__ == "__main__":
main()
Remplacez le slug par google/nano-banana-pro/edit-image, ajoutez image_urls, et le même script édite au lieu de générer. Remplacez-le par Nano Banana 2 ou Nano Banana 2 Lite et il tourne encore, parce que l'enveloppe est identique sur tout ce qui est dans la catégorie text-to-image et tout ce qui est dans image-to-image. Seuls les champs d'input diffèrent, et l'article de comparaison des modèles traite lequel viser.
Une dernière chose qui n'est pas du code. Chaque image de chaque modèle Google porte un watermark SynthID et aucun provider ne peut le désactiver. Réglez ça avec la personne qui signe le contrat avant de construire par-dessus.
Questions fréquentes
Comment appeler l'API Nano Banana Pro ?
Envoyez un POST à https://api.e2x.ai/v1/jobs/submit avec un bearer token, le slug de modèle google/nano-banana-pro/text-to-image et un objet input contenant votre prompt. La réponse renvoie data.jobId. Faites du polling sur https://api.e2x.ai/v1/jobs/{id} ou passez un webhookUrl dans le corps du submit, puis lisez l'image finie dans data.outputs[0].url.
Quel est l'aspect ratio par défaut de Nano Banana Pro ?
9:16, c'est-à-dire du portrait. Ça surprend presque tout le monde, parce que la plupart des API image sont par défaut en carré. Si vous ne fixez pas aspect_ratio explicitement, chaque image sort verticale. Le modèle accepte 9:16, 16:9, 1:1, 2:3, 3:2, 21:9, 3:4, 4:3, 4:5 et 5:4.
Pourquoi l'URL de mon image Nano Banana Pro est-elle cassée ?
Parce que l'URL de sortie que nous renvoyons est une livraison temporaire, pas de l'hébergement permanent. Tout pipeline qui enregistre notre URL en base et l'affiche plus tard montrera des images cassées une fois l'objet expiré. Téléchargez les octets dans la même étape que celle qui traite la complétion du job, et stockez-les dans votre propre bucket.
Faut-il utiliser le polling ou les webhooks avec l'API E2X ?
Faites du polling pendant que vous développez, parce que vous le testez depuis un terminal. Passez aux webhooks pour tout ce qui tourne en production. Un job Nano Banana Pro prend une trentaine de secondes, donc un batch de deux cents veut dire deux cents boucles de polling concurrentes qui perdent toutes leur état au deploy suivant. Avec webhookUrl, le job ID part dans votre base au moment du submit et le handler le reprend plus tard.
Comment éditer une image existante avec Nano Banana Pro ?
Utilisez le slug google/nano-banana-pro/edit-image et ajoutez un tableau image_urls à l'objet input, à côté de votre prompt. Les URL doivent être publiquement récupérables : une URL signée depuis votre stockage fonctionne, un chemin local non. L'édition coûte les mêmes $0.075 que la génération.
Quelle résolution demander à Nano Banana Pro ?
Envoyez 2k. Les deux premiers réglages partagent une même facturation plate sur ce slug, donc 1k est strictement dominé — moins de pixels, ligne de facture identique. Ne visez 4k que si vous avez vraiment besoin du plafond 4096×4096, puisqu'il double la facturation à $0.15. Les trois valeurs sont en minuscules.
Pourquoi l'API E2X renvoie-t-elle des prix comme 75000 ?
Toutes les valeurs monétaires de l'API sont exprimées en micro-cents, où 1 000 000 égale un dollar américain. Un 75000 sur un job Nano Banana Pro vaut $0.075. Gardez l'entier en stockage et ne divisez qu'au point où un humain le lit, pour que les arrondis ne s'accumulent jamais sur une période de facturation.