Générer des images automatiquement : templates, jobs et webhooks
La plupart des réponses à cette question sont une capture d'écran d'un champ de saisie. Tapez un prompt, cliquez, attendez, téléchargez, recommencez. Ce n'est pas de l'automatisation. C'est vous, à la main, plus vite.
Automatique veut dire aucun humain dans la boucle. Une ligne de données arrive, une image sort, personne n'a regardé. Ça demande quatre pièces : un template de prompt percé de trous, des données pour remplir les trous, une file qui accepte du travail sans bloquer, et un endroit durable où poser le résultat. Nous opérons l'API au milieu de tout ça, alors nous voyons les pipelines que les gens construisent — et ceux qui cassent en silence trois semaines après le lancement.

Ce que personne ne dit en premier : c'est devenu bon marché
Commencez par le chiffre, parce qu'il change ce que vous êtes prêt à construire.
Mille images via Nano Banana 2 Lite coûtent $23.80. Soit 1 000 × $0.0238, à notre prix du 26 août 2026. Cent mille photos produit — un vrai catalogue, chaque SKU, chaque coloris — font $2,380.
Les mêmes volumes sur les paliers entre lesquels vous arbitreriez :
| Images | Nano Banana 2 Lite | Nano Banana 2 | Nano Banana Pro |
|---|---|---|---|
| 100 | $2.38 | $4.00 | $7.50 |
| 1 000 | $23.80 | $40.00 | $75.00 |
| 10 000 | $238.00 | $400.00 | $750.00 |
| 100 000 | $2,380.00 | $4,000.00 | $7,500.00 |
Les prix et les conditions produit peuvent changer. Vérifiez les tarifs en vigueur de chaque provider avant toute décision d'achat. La tarification Google Batch ou Flex n'est pas directement équivalente à une requête API on-demand standard, car les conditions de planification, de disponibilité et de traitement diffèrent ; elle est donc exclue de cette comparaison. Il s'agit d'une comparaison délimitée, et non d'une affirmation selon laquelle E2X serait l'option la moins chère au monde dans toutes les configurations.
La plupart des gens qui demandent comment automatiser la génération d'images l'ont silencieusement classée dans « trop cher pour essayer ». C'est une question à $23.80. Construisez le truc.
Étape 1 : arrêtez d'écrire des prompts, écrivez un template
Un prompt que vous tapez une fois est une phrase. Un prompt qu'une machine exécute dix mille fois est un template à slots, et toute la difficulté est dans cette différence.
La règle : tout ce qui varie va dans un slot, tout ce qui doit rester identique va dans le texte figé. Ratez cette frontière et la série dérive — la moitié des images sur fond blanc, l'autre sur fond gris, parce que « le fond » a fui dans la partie variable.
TEMPLATE = (
"Photographie produit en studio : {product_name} en {colour}, "
"cadrage centré sur un fond gris chaud uni, lumière principale douce et directionnelle "
"venant du haut à gauche, faible profondeur de champ, style catalogue éditorial, "
"sans texte, sans logos, sans personnes"
)
Deux slots. L'éclairage, le fond, le cadrage et les exclusions sont gelés. C'est cette moitié gelée qui fait que 400 images ressemblent à une seule séance photo plutôt qu'à 400 accidents.
Gardez quatre choses hors de la moitié variable : le médium, la direction de la lumière, le fond et les instructions négatives. C'est votre identité visuelle. Si elles bougent d'une ligne à l'autre, vous n'en avez plus.

L'écriture de prompt en elle-même fait l'objet d'un autre article. Ce qui compte pour l'automatisation, c'est la forme : cadre figé, trous nommés, aucune surprise.
Étape 2 : les données sont le vrai produit
Le template est trivial. C'est dans les données que se trouve le travail, et vous les avez généralement déjà.
- Une table de catalogue produit. Une ligne par SKU, des colonnes pour le nom, la couleur, la matière, la catégorie.
- Un export CMS d'articles de blog, où le titre et le sujet deviennent les slots d'une image d'en-tête par article.
- Des lignes dans la base de votre application — annonces créées par les utilisateurs, pages d'événement, recettes.
- Un tableur de variantes publicitaires : six accroches par quatre fonds font vingt-quatre images, et personne ne briefe ça à la main.
Normalisez la source, quelle qu'elle soit, en une liste de dictionnaires avant qu'elle ne touche l'API, et gardez un ID stable sur chaque ligne. Vous en avez besoin pour raccrocher les résultats aux enregistrements, et l'avoir sauté fait mal exactement au moment où un webhook se déclenche.
Étape 3 : soumettez, n'attendez pas
Notre API est asynchrone à dessein. Vous POSTez un job, vous récupérez un job ID immédiatement, et la génération se passe de notre côté.
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-2-lite/text-to-image",
"input": {
"prompt": "Photographie produit en studio : lampe de bureau en noyer, finition noir mat...",
"aspect_ratio": "1:1"
},
"webhookUrl": "https://your-app.example.com/hooks/e2x"
}'
Le walker qui transforme une table en jobs fait une trentaine de lignes. Il lit un CSV, formate le template ligne par ligne, soumet, et note le job ID en face de l'ID de ligne pour que rien ne soit orphelin.
import csv
import os
import time
import requests
API = "https://api.e2x.ai/v1"
HEADERS = {
"Authorization": f"Bearer {os.environ['E2X_API_KEY']}",
"Content-Type": "application/json",
}
TEMPLATE = (
"Photographie produit en studio : {product_name} en {colour}, "
"cadrage centré sur un fond gris chaud uni, lumière principale douce et directionnelle "
"venant du haut à gauche, faible profondeur de champ, style catalogue éditorial, "
"sans texte, sans logos, sans personnes"
)
MAX_IMAGES = 500 # plafond dur, voir la section coûts
UNIT_COST = 0.0238 # Nano Banana 2 Lite, vérifié le 26 août 2026
def submit(row):
body = {
"model": "google/nano-banana-2-lite/text-to-image",
"input": {
"prompt": TEMPLATE.format(**row),
"aspect_ratio": "1:1",
},
"webhookUrl": "https://your-app.example.com/hooks/e2x",
}
for attempt in range(4):
r = requests.post(f"{API}/jobs/submit", json=body, headers=HEADERS, timeout=30)
if r.status_code < 300:
return r.json()["data"]["jobId"]
if r.status_code == 429 or r.status_code >= 500:
time.sleep(2 ** attempt)
continue
raise RuntimeError(f"ligne {row['id']} rejetée : {r.status_code} {r.text}")
raise RuntimeError(f"ligne {row['id']} toujours en échec après 4 tentatives")
with open("catalog.csv") as fh:
rows = list(csv.DictReader(fh))[:MAX_IMAGES]
print(f"soumission de {len(rows)} jobs, budget ${len(rows) * UNIT_COST:.2f}")
submitted = {}
for row in rows:
try:
submitted[row["id"]] = submit(row)
except RuntimeError as err:
print(f"ignorée : {err}")
print(f"{len(submitted)}/{len(rows)} acceptées")
Notez ce que cette boucle ne fait pas : s'arrêter quand une ligne échoue. Un batch de 500 où la ligne 212 a une couleur nulle doit vous rendre 499 images et un skip journalisé, pas une stack trace à 3 h du matin et zéro image.
Le slug compte plus qu'il n'en a l'air. google/nano-banana-2-lite/text-to-image porte le suffixe de capability ; le slug texte de Nano Banana 2 est google/nano-banana-2 tout nu, sans rien. Deviner vous coûte un 404 sur un pipeline qui a par ailleurs l'air terminé. Chaque modèle publie sa liste de paramètres dans un fichier de spec lisible par machine.
Étape 4 : le webhook plutôt que le polling, et voici pourquoi
Vous pouvez faire du polling. GET /v1/jobs/{JOB_ID} renvoie pending, processing, completed, failed ou cancelled, et pour un script ponctuel ça va très bien.
Pour un pipeline, c'est le mauvais choix, et la raison n'est pas l'élégance. Le polling couple la durée de vie de votre processus au job le plus lent du batch. Une génération Lite tourne autour de 60 secondes chez nous. Soumettez-en 500 et le poller doit rester en vie, à porter de l'état, aussi longtemps que dure la traîne — à travers un deploy, un redémarrage de conteneur, le timeout de 15 minutes de la plateforme serverless sur laquelle vous êtes. Quand il meurt, il meurt en emportant le seul relevé des jobs en vol.
Un webhook inverse tout ça. Vous soumettez et vous sortez. Plus tard, un POST arrive avec un job terminé et vous traitez exactement ce job-là, sans état.
// Handler Express. Idempotent : le même jobId peut arriver deux fois.
app.post("/hooks/e2x", express.json(), async (req, res) => {
res.status(200).end(); // acquitter d'abord, travailler ensuite
const job = req.body.data;
if (job.status !== "completed") {
await markFailed(job.jobId, job.error?.message ?? "inconnu");
return;
}
const already = await findAsset(job.jobId);
if (already) return;
await storeImage(job.jobId, job.outputs[0].url);
});
Faites du polling quand vous lancez un script à la main et que vous le regardez tourner. Prenez le webhook quand personne ne regarde, ce qui est la définition de l'automatisation.
Étape 5 : l'URL de sortie est temporaire, et c'est comme ça que les pipelines cassent
C'est celle-là. Si vous survolez tout le reste, lisez cette section deux fois.
data.outputs[0].url est une URL de livraison, pas du stockage. Elle marche maintenant. Elle ne marchera pas éternellement. Tout ce qui la traite comme permanente — une colonne product_image_url, un champ de CMS, un lien envoyé par mail à un client — s'affiche parfaitement en staging et montre des icônes d'image cassée quinze jours plus tard.
La panne est vicieuse parce qu'elle est différée. Rien n'échoue à l'écriture. Les tests passent. Le batch annonce 500 succès. Puis les images commencent à disparaître, en partant des enregistrements les plus anciens, et le temps que quelqu'un s'en aperçoive vous ne pouvez plus les regénérer sans rejouer tout le job, parce que les prompts non plus n'avaient jamais été sauvegardés.
Téléchargez les octets. Posez-les dans votre propre bucket. Stockez votre URL.

import requests
import boto3
s3 = boto3.client("s3")
def store_image(job_id, row_id, source_url):
img = requests.get(source_url, timeout=60)
img.raise_for_status()
key = f"products/{row_id}.jpg"
s3.put_object(
Bucket="my-assets",
Key=key,
Body=img.content,
ContentType=img.headers.get("content-type", "image/jpeg"),
)
return f"https://cdn.example.com/{key}"
Pendant que vous y êtes, sauvegardez le prompt à côté de l'image. Une colonne texte, et c'est toute la différence entre « regénère cette variante » et « on recommence tout ».
Deux autres habitudes à prendre dès le premier jour. Vérifiez que le téléchargement a bien renvoyé des octets d'image plutôt qu'une page d'erreur, parce qu'un 200 contenant du HTML s'écrira très volontiers dans votre bucket. Et indexez le stockage sur votre ID de ligne, pas sur notre job ID, pour qu'une regénération écrase le bon objet au lieu de l'orpheliner.
Étape 6 : ce qui casse vraiment à grand volume
Les petits batchs cachent tout. Voici ce qui apparaît une fois passées quelques centaines de lignes.
Les batchs partiels sont normaux. Une fraction de tout gros run échoue — un prompt qui déclenche un filtre de contenu, une ligne malformée, une erreur upstream transitoire. Traitez 497 sur 500 comme le cas de succès, et donnez-vous un retry en une commande sur ces trois lignes-là uniquement.
Les retries ont besoin d'un plafond de dépense. Une boucle de retry sans limite est un bug qui facture. $0.0238 le coup paraît inoffensif jusqu'à ce qu'une boucle bloquée le fasse quarante mille fois. Mettez un compteur dur dans le code — la ligne MAX_IMAGES plus haut est l'assurance la moins chère du lot.
L'idempotence n'est pas optionnelle. Un webhook peut être livré plus d'une fois. Un handler qui ne supporte pas d'être rejoué deux fois sur un même job ID vous donne des assets en double et des lignes en double, et vous le découvrez pendant la semaine la plus chargée.
Les coûts réclament un compteur en direct, pas une facture mensuelle. Les valeurs monétaires de notre API reviennent en micro-cents — 1 000 000 égale $1. Sommez-les par batch et journalisez le total à la clôture. Un pipeline qui annonce « 412 images, $9.81 » tous les soirs est un pipeline que personne n'a besoin d'auditer.
Les valeurs par défaut d'aspect ratio vont vous surprendre. Lite est par défaut en 1:1, tandis que Nano Banana 2 et Pro sont par défaut en 9:16. Fixez-la dans le template ou la moitié de votre catalogue devient silencieusement du portrait.
Le watermark voyage avec le fichier. Google incruste SynthID dans tout ce que produisent ses modèles image. C'est invisible, ce n'est pas un flag, et ça survit à tout ce que votre pipeline fera ensuite des octets. Qui que ce soit qui tienne la marque ou le juridique chez vous doit valider les assets générés par IA avant que le pipeline n'existe, pas après qu'il a écrit 20 000 objets dans un bucket.

Vers quel modèle pointer le pipeline
Par défaut, Nano Banana 2 Lite, et n'en partez que quand quelque chose de précis vous y force.
C'est de l'arithmétique, pas une prudence de façade. À $0.0238, c'est la chose la moins chère que nous vendions qui fabrique une image, une génération entière plus récente que le Nano Banana original qu'elle remplace, et à des volumes de pipeline l'écart de prix se compose jusqu'à devenir le seul chiffre que la finance regarde. Il porte aussi quatre aspect ratios que personne d'autre n'a — 1:4, 4:1, 1:8 et 8:1 — exactement ce qu'il vous faut si la sortie, ce sont des bannières et des bandeaux verticaux. Une bizarrerie : aucun paramètre resolution, et en envoyer un est une erreur, pas un no-op silencieux.
Détournez-vous de lui dans ces quatre cas, et pas autrement :
- Une sortie au-dessus de la 1K. Lite est en 1K uniquement. Nano Banana 2 atteint la 4K à $0.04 de base, avec la 2K à ×1,5 et la 4K à ×2.
- Du texte lisible dans l'image. Fiches produit avec prix, infographies localisées. Ça vit sur Nano Banana Pro à $0.075, où la 1K et la 2K coûtent pareil — prenez la 2K, elle est gratuite.
- Des fonds transparents. Les détourages et les planches de stickers demandent
background: transparent, et GPT Image 2 est le seul modèle de notre catalogue à le faire en un appel. Budgétez $0.0525 en 1K, pas le chiffre catalogue brut —qualitymultiplie par 7 à tous les paliers. - Le même visage ou la même bouteille sur beaucoup d'images. Un problème d'images de référence, pas un problème de volume, avec son propre article sur la cohérence.
Pour des charges mixtes, faites tourner le gros sur Lite et branchez les 2 % qui réclament du texte ou de la fidélité vers Pro, à partir d'une colonne de vos données. Un pipeline, deux modèles, et le tarif Pro seulement là où il rapporte quelque chose.
Pour le déroulé paramètre par paramètre du contrat submit et poll, voyez le tutoriel API Nano Banana Pro, et pour le détail palier par palier, la comparaison des modèles. Ces deux-là supposent que vous avez déjà choisi un modèle. Si ce n'est pas fait, les modèles text-to-image sont listés avec leurs prix, et le catalogue y ajoute le versant vidéo.
Le tout, dans l'ordre
Un template à slots. Des données avec des ID stables. Une soumission en jobs sous un plafond dur. Un webhook. Le téléchargement des octets dans votre propre stockage. La journalisation des échecs et de la dépense. Six étapes, aucune difficile, et celle qu'on saute est toujours la cinquième.
Questions fréquentes
Comment génère-t-on une image automatiquement ?
Écrivez votre prompt comme un template à slots nommés, remplissez ces slots depuis une source de données — catalogue produit, CSV — et soumettez chaque prompt rempli comme un job asynchrone à une API image. Récupérez le résultat sur un webhook plutôt qu'en polling, puis téléchargez les octets de l'image dans votre propre stockage. Rien dans cette boucle ne réclame un humain, et c'est ce qui la rend automatique plutôt que manuelle-mais-rapide.
Combien coûte la génération automatique de 1 000 images ?
Aux prix E2X du 26 août 2026, 1 000 images via Nano Banana 2 Lite font $23.80, puisque chaque requête est à $0.0238. Le même volume fait $40.00 sur Nano Banana 2 et $75.00 sur Nano Banana Pro. Les résolutions supérieures multiplient par-dessus, mais pour une sortie 1K standard le chiffre par image est tout le calcul.
Faut-il un webhook ou du polling pour les jobs de génération d'images ?
Un webhook pour tout ce qui tourne sans surveillance. Le polling lie la durée de vie de votre processus au job le plus lent du batch, donc un deploy ou un timeout serverless tue le poller pendant qu'il détient le seul relevé des jobs encore en cours. Un webhook vous laisse soumettre puis sortir, et traiter chaque job terminé sans état. Le polling convient pour un script que vous regardez tourner.
Pourquoi mes images générées cessent-elles de s'afficher au bout d'un moment ?
Parce que vous avez stocké l'URL de sortie de l'API au lieu de l'image. Cette URL est une livraison temporaire, pas du stockage : une colonne qui la contient s'affiche correctement au début et casse plus tard. Téléchargez les octets dès que le job est terminé, écrivez-les dans votre propre bucket, stockez votre propre URL. C'est la façon la plus répandue de livrer un pipeline d'images déjà cassé.
Sur quel modèle un pipeline d'images automatisé doit-il tourner par défaut ?
Nano Banana 2 Lite, à $0.0238 l'image. C'est le modèle image le moins cher de notre catalogue et une génération plus récent que le Nano Banana original, et à des volumes de batch le prix domine toutes les autres considérations. Passez à Nano Banana 2 uniquement pour une sortie au-dessus de la 1K, à Nano Banana Pro uniquement pour du texte lisible dans l'image, et à GPT Image 2 uniquement pour des fonds transparents.
Que se passe-t-il quand certaines images d'un batch échouent ?
Il y en aura toujours, et le pipeline doit traiter ça comme normal plutôt que fatal. Continuez à soumettre après une ligne rejetée, journalisez les ID en échec avec leurs erreurs, et construisez un chemin de retry qui ne rejoue que les échecs. Plafonnez les tentatives, puisque chacune est une vraie facturation.