Como gerar imagens automaticamente: templates, jobs e webhooks
A maioria das respostas a essa pergunta é o print de uma caixa de texto. Digite um prompt, aperte um botão, espere, baixe, repita. Isso não é automação. Isso é você, trabalhando na mão, mais rápido.
Automático quer dizer nenhum humano no meio. Uma linha de dados chega, uma imagem sai, ninguém assistiu acontecer. Isso exige quatro partes: um template de prompt com buracos, dados para preencher os buracos, uma fila que aceita trabalho sem bloquear, e algum lugar durável para colocar o resultado. Rodamos a API que fica no meio disso, então vemos os pipelines que as pessoas constroem e os que quebram em silêncio três semanas depois de entrar no ar.

A parte que ninguém te conta primeiro: hoje isso é barato
Comece pelo número, porque ele muda o que você se dispõe a construir.
Mil imagens pelo Nano Banana 2 Lite custam $23.80. É 1.000 × $0.0238, no nosso preço de 26 de agosto de 2026. Cem mil fotos de produto — um catálogo de verdade, todo SKU, toda variação de cor — dão $2,380.
Os mesmos volumes nos tiers entre os quais você escolheria:
| Imagens | 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 |
Os preços e as condições dos produtos podem mudar. Verifique o preço atual de cada provider antes de tomar uma decisão de compra. O preço do Google Batch ou Flex não é diretamente equivalente a um request de API on-demand padrão, porque as condições de agendamento, disponibilidade e processamento são diferentes; por isso ele ficou fora desta comparação. Esta é uma comparação de escopo limitado, não uma afirmação de que a E2X é a opção mais barata do mundo em qualquer configuração.
A maior parte das pessoas que perguntam como automatizar geração de imagem já precificou isso em silêncio como "caro demais para tentar". É uma pergunta de $23.80. Construa a coisa.
Passo 1: pare de escrever prompts, comece a escrever um template
Um prompt que você digita uma vez é uma frase. Um prompt que uma máquina roda dez mil vezes é um template com slots, e essa diferença é o trabalho inteiro.
A regra: tudo que varia vai num slot, tudo que precisa ficar idêntico vai no texto fixo. Erre essa fronteira e o conjunto deriva — metade das imagens no branco, metade no cinza, porque "fundo" vazou para a parte variável.
TEMPLATE = (
"Studio product photograph of a {product_name} in {colour}, "
"centred on a plain warm-grey backdrop, soft directional key light "
"from the upper left, shallow depth of field, editorial catalog styling, "
"no text, no logos, no people"
)
Dois slots. A iluminação, o fundo, o enquadramento e as exclusões estão congelados. Essa metade congelada é o que faz 400 imagens parecerem um ensaio fotográfico em vez de 400 acidentes.
Mantenha quatro coisas fora da metade variável: o meio, a direção da luz, o fundo e as instruções negativas. Essas são a sua identidade visual. Se elas se mexem a cada linha, você não tem nenhuma.

O ofício do prompt em si é um post à parte. O que importa para automação é o formato: moldura fixa, buracos nomeados, nenhuma surpresa.
Passo 2: os dados são o produto de verdade
O template é trivial. É nos dados que o trabalho mora, e normalmente é algo que você já tem.
- Uma tabela de catálogo de produtos. Uma linha por SKU, colunas para nome, cor, material, categoria.
- Uma exportação de posts de blog do CMS, em que título e tema viram os slots para uma imagem de header por artigo.
- Linhas no banco de dados da sua aplicação — anúncios criados por usuários, páginas de evento, receitas.
- Uma planilha de variantes de anúncio: seis títulos por quatro fundos são vinte e quatro imagens, e ninguém faz esse briefing na mão.
Normalize a fonte, seja ela qual for, para uma lista de dicionários antes de ela encostar na API, e mantenha um ID estável em toda linha. Você precisa dele para casar os resultados com os registros, e pular isso dói exatamente no momento em que um webhook dispara.
Passo 3: submeta, não espere
A nossa API é assíncrona de propósito. Você faz POST de um job, recebe um job ID de volta na hora, e a geração acontece do nosso lado.
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": "Studio product photograph of a walnut desk lamp in matte black...",
"aspect_ratio": "1:1"
},
"webhookUrl": "https://your-app.example.com/hooks/e2x"
}'
O walker que transforma uma tabela em jobs tem umas trinta linhas. Ele lê um CSV, formata o template por linha, submete, e registra o job ID contra o ID da linha para nada ficar órfão.
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 = (
"Studio product photograph of a {product_name} in {colour}, "
"centred on a plain warm-grey backdrop, soft directional key light "
"from the upper left, shallow depth of field, editorial catalog styling, "
"no text, no logos, no people"
)
MAX_IMAGES = 500 # teto duro, veja a seção de custo
UNIT_COST = 0.0238 # Nano Banana 2 Lite, conferido em 26 de agosto de 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"row {row['id']} rejected: {r.status_code} {r.text}")
raise RuntimeError(f"row {row['id']} still failing after 4 attempts")
with open("catalog.csv") as fh:
rows = list(csv.DictReader(fh))[:MAX_IMAGES]
print(f"submitting {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"skipped: {err}")
print(f"{len(submitted)}/{len(rows)} accepted")
Repare no que esse loop não faz: parar quando uma linha falha. Um batch de 500 em que a linha 212 tem cor nula deveria te dar 499 imagens e um skip registrado, não um stack trace às 3 da manhã e zero imagem.
O slug importa mais do que parece. google/nano-banana-2-lite/text-to-image carrega o sufixo de capability; o slug de texto do Nano Banana 2 é google/nano-banana-2 puro, sem nenhum. Chutar te custa um 404 num pipeline que fora isso parece pronto. Cada modelo publica a lista de parâmetros dele num arquivo de spec legível por máquina.
Passo 4: webhook em vez de polling, e aqui está o motivo
Dá para fazer polling. GET /v1/jobs/{JOB_ID} devolve pending, processing, completed, failed ou cancelled, e para um script avulso isso serve.
Para um pipeline é a escolha errada, e o motivo não é elegância. O polling acopla o tempo de vida do seu processo ao job mais lento do batch. Uma geração no Lite leva mais ou menos 60 segundos do nosso lado. Submeta 500 e o poller tem que ficar vivo, segurando estado, pelo tempo que a cauda levar — atravessando um deploy, o restart de um container, o timeout de 15 minutos da plataforma serverless em que você estiver. Quando ele morre, morre segurando o único registro de quais jobs estavam em voo.
Um webhook inverte isso. Você submete e sai. Depois, chega um POST com um job concluído e você trata exatamente aquele job, sem estado.
// Handler Express. Idempotente: o mesmo jobId pode chegar duas vezes.
app.post("/hooks/e2x", express.json(), async (req, res) => {
res.status(200).end(); // confirme primeiro, trabalhe depois
const job = req.body.data;
if (job.status !== "completed") {
await markFailed(job.jobId, job.error?.message ?? "unknown");
return;
}
const already = await findAsset(job.jobId);
if (already) return;
await storeImage(job.jobId, job.outputs[0].url);
});
Faça polling quando estiver rodando um script na mão e olhando para ele. Pegue o webhook quando ninguém estiver olhando, que é a definição de automação.
Passo 5: a URL de saída é temporária, e é assim que os pipelines quebram
Essa é a seção. Se você passar os olhos por todo o resto, leia esta duas vezes.
data.outputs[0].url é uma URL de entrega, não de armazenamento. Funciona agora. Não vai funcionar para sempre. Qualquer coisa que trate ela como permanente — uma coluna product_image_url, um campo de CMS, um link enviado por e-mail a um cliente — renderiza bonito em staging e mostra ícone de imagem quebrada duas semanas depois.
A falha é cruel porque é adiada. Nada dá erro na hora da escrita. Os testes passam. O batch reporta 500 sucessos. Aí as imagens começam a sumir a partir dos registros mais antigos, e quando alguém percebe você já não consegue regerar elas sem rodar o job inteiro de novo, porque os prompts também nunca foram salvos.
Baixe os bytes. Coloque eles no seu próprio bucket. Guarde a sua 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}"
Já que você está aí, salve o prompt junto com a imagem. Uma coluna de texto, e ela é a diferença entre "regere aquela variante" e "comece do zero".
Mais dois hábitos que vale ter desde o primeiro dia. Confira se o download devolveu bytes de imagem e não uma página de erro, já que um 200 com HTML dentro vai ser escrito no seu bucket sem reclamar. E indexe o armazenamento pelo ID da sua linha, não pelo nosso job ID, para que uma regeneração sobrescreva o objeto certo em vez de deixar ele órfão.
Passo 6: o que dá errado de fato no volume
Batches pequenos escondem tudo. Aqui está o que aparece depois de algumas centenas.
Batches parciais são normais. Alguma fração de qualquer rodada grande falha — um prompt que esbarra num filtro de conteúdo, uma linha malformada, um erro upstream transitório. Trate 497 de 500 como o caso de sucesso, e se dê uma retentativa de um comando só sobre aquelas três linhas.
Retentativas precisam de um teto de gasto. Um loop de retry sem limite é um bug que fatura. $0.0238 por disparo soa inofensivo até um loop travado rodar quarenta mil vezes. Ponha uma contagem dura no código — a linha MAX_IMAGES lá em cima é o seguro mais barato aqui.
Idempotência não é opcional. Webhooks podem ser entregues mais de uma vez. Um handler que não seja seguro para rodar duas vezes num mesmo job ID te dá assets duplicados e linhas duplicadas, e você descobre isso na semana mais cheia.
Custos precisam de um contador ao vivo, não de uma fatura mensal. Os valores monetários na nossa API voltam em micro-centavos — 1.000.000 é igual a $1. Some eles por batch e registre o total quando ele fechar. Um pipeline que reporta "412 imagens, $9.81" toda noite é um pipeline que ninguém precisa auditar.
Os defaults de aspect ratio vão te surpreender. O Lite tem default 1:1, enquanto o Nano Banana 2 e o Pro têm default 9:16. Fixe isso no template ou metade do seu catálogo vira retrato em silêncio.
O watermark viaja junto com o arquivo. O Google carimba o SynthID em tudo que os modelos de imagem dele produzem. É invisível, não é uma flag, e sobrevive ao que quer que o seu pipeline faça com os bytes depois. Quem responde por marca ou jurídico do seu lado deveria aprovar assets gerados por IA antes de o pipeline existir, não depois de ele ter escrito 20.000 objetos num bucket.

Para qual modelo apontar o pipeline
Deixe o Nano Banana 2 Lite como default e só saia dele quando algo específico te obrigar.
Isso é aritmética, não hesitação. A $0.0238 ele é a coisa mais barata que vendemos que faz uma imagem, uma geração inteira mais novo que o Nano Banana original que ele substituiu, e em volume de pipeline a diferença de preço se acumula no único número para o qual o financeiro olha. Ele também carrega quatro aspect ratios que mais nada tem — 1:4, 4:1, 1:8 e 8:1 — exatamente o que você quer se a saída for banners e faixas verticais. Uma peculiaridade: nenhum parâmetro resolution, e mandar um é erro, não um no-op silencioso.
Desvie dele nestes quatro casos, e em nenhum outro:
- Saída acima de 1K. O Lite só faz 1K. O Nano Banana 2 chega a 4K com base de $0.04, com 2K a ×1,5 e 4K a ×2.
- Texto legível dentro da imagem. Cards de produto com preço, infográficos localizados. Isso mora no Nano Banana Pro a $0.075, onde 1K e 2K custam o mesmo — pegue o 2K, é de graça.
- Fundos transparentes. Recortes e cartelas de sticker precisam de
background: transparent, e o GPT Image 2 é o único modelo que carregamos que faz isso numa chamada só. Faça o orçamento com $0.0525 em 1K, não com o número cru de catálogo —qualitymultiplica por 7 em todo tier. - O mesmo rosto ou a mesma garrafa em vários quadros. É um problema de imagem de referência, não de volume, e tem um post próprio sobre consistência.
Para cargas mistas, rode o grosso no Lite e desvie os 2% que precisam de texto ou fidelidade para o Pro a partir de uma coluna nos seus dados. Um pipeline, dois modelos, preço de Pro só onde ele rende alguma coisa.
Para o passo a passo parâmetro por parâmetro do contrato de submit e polling, veja o tutorial da API do Nano Banana Pro, e para o detalhamento tier por tier, a comparação de modelos. Os dois assumem que você já escolheu um modelo. Se ainda não escolheu, os modelos text-to-image estão listados com os preços junto, e o catálogo acrescenta o lado de vídeo.
A coisa inteira, na ordem
Template com slots. Dados com IDs estáveis. Submeta como jobs sob um teto duro. Pegue um webhook. Baixe os bytes para o seu próprio armazenamento. Registre as falhas e o gasto. Seis passos, nenhum deles difícil, e o que sempre é pulado é o passo cinco.
Perguntas frequentes
Como gerar uma imagem automaticamente?
Escreva o seu prompt como um template com slots nomeados, preencha esses slots a partir de uma fonte de dados como um catálogo de produtos ou um CSV, e submeta cada prompt preenchido como um job assíncrono a uma API de imagem. Pegue o resultado por webhook em vez de polling, depois baixe os bytes da imagem para o seu próprio armazenamento. Nada nesse loop precisa de um humano, que é o que faz dele automático em vez de trabalho manual rápido.
Quanto custa gerar 1.000 imagens automaticamente?
Aos preços da E2X em 26 de agosto de 2026, 1.000 imagens pelo Nano Banana 2 Lite dão $23.80, já que cada request custa $0.0238. O mesmo volume dá $40.00 no Nano Banana 2 e $75.00 no Nano Banana Pro. Resoluções maiores multiplicam por cima, mas para saída padrão em 1K o número por imagem é a conta inteira.
Devo usar webhook ou polling para jobs de geração de imagem?
Use webhook para qualquer coisa que rode sem supervisão. O polling amarra o tempo de vida do seu processo ao job mais lento do batch, então um deploy ou um timeout de serverless mata o poller enquanto ele segura o único registro de quais jobs ainda estão rodando. Um webhook deixa você submeter e sair, e depois tratar cada job concluído sem estado. Polling serve para um script que você está vendo rodar.
Por que as minhas imagens geradas param de carregar depois de um tempo?
Porque você guardou a URL de saída da API em vez da imagem. Essa URL é entrega temporária, não armazenamento, então uma coluna que a guarda renderiza certo no começo e quebra depois. Baixe os bytes assim que o job terminar, escreva eles no seu próprio bucket, guarde a sua própria URL. É a forma mais comum de um pipeline de imagem entrar no ar quebrado.
Para qual modelo um pipeline automatizado de imagem deve apontar por default?
Nano Banana 2 Lite, a $0.0238 por imagem. É o modelo de imagem mais barato do nosso catálogo e uma geração mais novo que o Nano Banana original, e em volume de batch o preço domina qualquer outra consideração. Mude para o Nano Banana 2 só para saída acima de 1K, para o Nano Banana Pro só para texto legível dentro da imagem, e para o GPT Image 2 só para fundos transparentes.
O que acontece quando algumas imagens de um batch falham?
Sempre algumas vão falhar, e o pipeline deveria tratar isso como normal, não como fatal. Continue submetendo depois de uma linha rejeitada, registre os IDs que falharam com os erros deles, e construa um caminho de retentativa que rode só as falhas. Limite as tentativas de retry, já que cada uma é uma cobrança real.