Como chamar a API do Nano Banana Pro: o passo a passo completo
Esta é a integração inteira, do início ao fim, do Nano Banana Pro na API da E2X. Pegue uma key, submeta um job, leia a response, espere direito, trate as formas como ele pode falhar, e coloque os bytes em algum lugar que ainda vá estar lá amanhã.
Esse último passo é o que pega as pessoas. Pule para ele se você já está gerando imagens e só quer saber por que as suas sumiram.

Se o Pro é o tier certo para o seu trabalho é outra pergunta e respondemos ela à parte, em o post de preço e velocidade. Versão curta: $0.075 por request, uns 30 segundos, vale a pena se as suas imagens carregam texto legível ou combinam referências que cumprem papéis diferentes. Esta página assume que você já decidiu.
O que você precisa antes do primeiro request
Quatro coisas, e três delas são uma linha cada.
- Uma API key. Crie uma pela sua conta E2X. Tudo abaixo manda ela como
Authorization: Bearer $E2X_API_KEY. Mantenha ela do lado do servidor. Uma key em JavaScript de navegador é uma key que outra pessoa está gastando agora. - A base URL, que é
https://api.e2x.ai/v1para todo modelo do catálogo. - O slug certo. A geração é
google/nano-banana-pro/text-to-image. A edição égoogle/nano-banana-pro/edit-image. Essas strings vão no corpo do request literalmente, e não existe correspondência aproximada nelas. - Um prompt que valha a pena mandar. O Pro recompensa especificidade mais que os tiers flash, porque tem mais espaço para agir sobre ela. Temos um guia de prompting à parte se você quiser ir além de "um gato, mas cinematográfico".
O contrato é assíncrono. Você submete um job, recebe um ID, e a imagem chega depois. Não existe endpoint síncrono que bloqueie até os pixels ficarem prontos e, dado que o Pro leva uns 30 segundos, você não ia querer um.
O seu primeiro 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": "Um menu de quadro-negro letrado à mão atrás de um balcão de café em mármore, luz quente de tarde vindo de uma janela à esquerda, profundidade de campo rasa",
"aspect_ratio": "16:9",
"resolution": "2k"
}
}'
A mesma coisa em 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"]
Os dois exemplos fixam aspect_ratio e resolution de propósito, e vale entender os dois defaults antes de você tirar isso.
O default de aspect_ratio é 9:16. Não 1:1. Se você deixar de fora, toda imagem que gerar sai em retrato, para sempre, e você não vai notar até um espaço largo do seu layout ficar errado. Esse é o campo da nossa API que mais derruba integração. Fixe ele.
Os valores de resolution são em minúsculas. 1k, 2k, 4k. O 2K maiúsculo não é a mesma string e a API vai te avisar.
Trate 1k como valor morto neste slug. As duas primeiras configurações ficam numa cobrança única e plana — $0.075 para qualquer uma, conferido em 26 de agosto de 2026 — então a menor só devolve menos pixels pela mesma linha da fatura. Só 4k mexe no número: $0.15, teto de 4096×4096.

O que volta
A chamada de submit devolve na hora um envelope de job. O campo que você precisa é o data.jobId:
{
"success": true,
"data": {
"jobId": "job_8Kd2mQvXpL",
"status": "pending"
}
}
O status vai de pending → processing → completed, ou cai numa das duas falhas terminais, failed e cancelled. Um job concluído carrega o resultado em data.outputs[0].url, e um que falhou carrega um motivo em data.error.message.
Um detalhe para internalizar antes de escrever qualquer código de cobrança: todo valor monetário que a API devolve está em micro-centavos. Um milhão é igual a um dólar. Um request do Nano Banana Pro volta portanto como 75000, não como 0.075. Divida por 1.000.000 na camada de exibição e em lugar nenhum além dela, e nunca guarde o número dividido.
Polling sem martelar
A versão bruta funciona:
curl https://api.e2x.ai/v1/jobs/job_8Kd2mQvXpL \
-H "Authorization: Bearer $E2X_API_KEY"
Embrulhe isso num loop com um sleep fixo de dois segundos e você tem uma integração funcionando. Para o Nano Banana Pro especificamente, um intervalo fixo é genuinamente aceitável — um request roda uns 30 segundos, então você faz mais ou menos quinze chamadas de status e para. Isso não é tráfego suficiente para incomodar ninguém.
Um backoff ainda é melhor, por uma razão que não tem nada a ver com educação. Intervalos fixos escondem variância. Se um job leva 90 segundos em vez de 30, um loop fixo continua chamando na mesma taxa e os seus logs ficam idênticos aos de uma rodada saudável. Um backoff que cresce deixa um job lento visivelmente 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")
Duas coisas que esse loop faz e um ingênuo normalmente não faz. Ele tem um prazo duro, então um job travado levanta erro em vez de girar até o processo ser morto. E ele trata os três estados terminais do mesmo jeito na camada de transporte, devolvendo o payload e deixando quem chamou decidir o que uma falha significa. Lógica de retentativa pertence acima dessa função, não dentro dela.

Webhooks, e por que você deveria mudar para eles
Passe webhookUrl no corpo do submit e a gente te chama quando o job chega a um estado terminal. Nenhum loop de 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": "Uma maquete arquitetônica de uma passarela sobre uma prancheta de desenho, luz do norte",
"aspect_ratio": "3:2",
"resolution": "2k"
},
"webhookUrl": "https://your-app.example.com/hooks/e2x"
}'
O polling é o primeiro movimento certo porque você consegue testar do terminal em dez segundos. É o estado permanente errado, e a razão é aritmética. Uma imagem por vez, o polling te custa um loop. Duzentas imagens num batch e o polling te custa duzentos loops concorrentes, cada um segurando uma conexão por meio minuto, num processo que agora não pode ser reiniciado sem perder o rastro de todo job em voo.
Webhooks tornam o trabalho reiniciável. O job ID vai para o seu banco quando você submete, o handler atualiza a linha quando a gente chama, e um deploy no meio não muda nada. Se você está construindo um pipeline de geração e não um script, é essa a versão para construir, e ela combina com os padrões do nosso post sobre automatizar geração de imagem de ponta a ponta.
Duas notas operacionais. O seu endpoint tem que ser alcançável pela internet pública, então uma URL de localhost simplesmente nunca vai disparar durante o desenvolvimento — use um túnel. E trate o webhook como uma notificação, não como fonte da verdade: busque o job pelo ID dentro do handler antes de agir sobre ele.
Editando uma imagem em vez de gerar uma
Mesmo envelope, outro slug, um campo a mais. O endpoint de edição aceita image_urls junto do prompt e custa os mesmos $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": "Substitua o fundo por um fundo infinito de estúdio cinza suave, mantenha a iluminação do produto exatamente como está",
"image_urls": ["https://your-cdn.example.com/source/bottle.jpg"],
"aspect_ratio": "1:1",
"resolution": "2k"
}
}'
As URLs que você passa precisam ser publicamente acessíveis. Uma URL assinada do seu próprio armazenamento funciona; um caminho no seu notebook não.
O Nano Banana Pro aceita até quatorze imagens de referência e, diferente dos outros tiers, separa elas por papel — até 5 para identidade de personagem, até 6 para fidelidade de objeto, até 3 para estilo. Essa divisão de papéis é a razão de estar neste tier, e os nomes exatos dos campos para referências com escopo de papel estão documentados por modelo no arquivo de spec legível por máquina, que é a autoridade se a nossa documentação e este post algum dia discordarem. Para o fluxo de consistência de personagem especificamente, aprofundamos em o guia de geração consistente.
Todos os parâmetros, e o default de cada um
| Campo | Onde vai | Valores aceitos | Default |
|---|---|---|---|
model | raiz do body | google/nano-banana-pro/text-to-image ou google/nano-banana-pro/edit-image | obrigatório |
input.prompt | dentro de input | string | obrigatório |
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 | fixe de forma explícita |
input.image_urls | dentro de input, só no slug de edição | array de URLs publicamente alcançáveis | obrigatório na edição |
webhookUrl | raiz do body | um endpoint HTTPS público | nenhum, faça polling |
As duas linhas que custam dinheiro às pessoas são aspect_ratio e resolution. Todo o resto se comporta do jeito que você imaginaria.
Quando o job não termina
Três superfícies de falha, e elas precisam de tratamentos diferentes.
A própria chamada de submit falha. Isso é um erro HTTP antes de qualquer job existir — uma key errada, um corpo malformado, um slug de modelo desconhecido. Nada foi enfileirado e nada foi cobrado. Conserte o request; repetir ele sem mudança vai falhar igual.
O job chega a failed. O job existiu e o modelo não produziu uma imagem. Leia data.error.message pelo motivo, que normalmente é uma recusa de política de conteúdo ou um input malformado, como uma URL inalcançável em image_urls. Uma retentativa cega de uma recusa de política falha do mesmo jeito; uma retentativa depois de um erro upstream transitório normalmente dá certo. Registre a mensagem, não registre só o status.
O job chega a cancelled. Terminal, e não é bug. Trate exatamente como failed no nível do código — a linha está fechada, saída nenhuma vem.
O seu loop de espera estoura o timeout. Isso não é um estado do job. Quer dizer que o job ainda está rodando e a sua paciência acabou. O job ID continua válido, então guarde ele e confira depois em vez de submeter de novo, o que te cobraria duas vezes pela mesma imagem.
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"]
Baixe a saída antes de ela expirar
Essa é a parte que entrega imagem quebrada uma semana depois do lançamento, então ela ganha uma seção própria.
A URL em data.outputs[0].url é temporária. É uma URL de entrega, não hospedagem. Se você escrever essa string numa coluna de banco chamada image_url e renderizar ela numa página de produto, a página funciona em staging, funciona na revisão, funciona no dia do lançamento, e depois vira ícone de imagem quebrada em silêncio assim que o objeto envelhece.
A correção é um passo e não é opcional. Busque os bytes, coloque eles no seu próprio armazenamento, guarde a sua 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, pelo shell:
curl -sL "$OUTPUT_URL" -o ./out/menu-board.jpg
Faça o download dentro da mesma unidade de trabalho que tratou a conclusão. Não num cron uma hora depois, não de forma preguiçosa na primeira visita à página. A janela é generosa o bastante para você escapar de um atraso nos testes e estreita o bastante para você não escapar sob carga.

A coisa inteira, como um script só
Tudo acima, montado. Defina E2X_API_KEY e rode.
#!/usr/bin/env python3
"""Gera uma imagem com o Nano Banana Pro e guarda ela localmente."""
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(
"Um menu de quadro-negro letrado à mão atrás de um balcão de café em "
"mármore, luz quente de tarde vindo de uma janela à esquerda, "
"profundidade de campo rasa"
)
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()
Troque o slug por google/nano-banana-pro/edit-image, acrescente image_urls e o mesmo script edita em vez de gerar. Troque pelo Nano Banana 2 ou pelo Nano Banana 2 Lite e ele ainda roda, porque o envelope é idêntico em tudo da categoria text-to-image e em tudo de image-to-image. Só os campos de input mudam, e o post de comparação de modelos cobre para qual apontar.
Uma última coisa que não é código. Toda imagem de todo modelo do Google carrega um watermark SynthID e nenhum provider consegue desativar. Resolva isso com quem assina o contrato antes de construir em cima.
Perguntas frequentes
Como eu chamo a API do Nano Banana Pro?
Mande um POST para https://api.e2x.ai/v1/jobs/submit com um bearer token, o slug de modelo google/nano-banana-pro/text-to-image e um objeto input contendo o seu prompt. A response devolve data.jobId. Faça polling em https://api.e2x.ai/v1/jobs/{id} ou passe um webhookUrl no corpo do submit, e depois leia a imagem pronta em data.outputs[0].url.
Qual é o aspect ratio default do Nano Banana Pro?
9:16, que é retrato. Isso surpreende quase todo mundo, porque a maioria das APIs de imagem tem default quadrado. Se você não definir aspect_ratio de forma explícita, toda imagem sai vertical. O modelo aceita 9:16, 16:9, 1:1, 2:3, 3:2, 21:9, 3:4, 4:3, 4:5 e 5:4.
Por que a URL da minha imagem do Nano Banana Pro está quebrada?
Porque a URL de saída que devolvemos é entrega temporária, não hospedagem permanente. Qualquer pipeline que salve a nossa URL num banco e renderize ela depois vai mostrar imagens quebradas assim que o objeto expirar. Baixe os bytes no mesmo passo que trata a conclusão do job e guarde eles no seu próprio bucket.
Devo usar polling ou webhooks com a API da E2X?
Faça polling enquanto está construindo, porque dá para testar do terminal. Mude para webhooks em qualquer coisa rodando em produção. Um job do Nano Banana Pro leva uns 30 segundos, então um batch de duzentos significa duzentos loops de polling concorrentes que perdem o estado no próximo deploy. Com webhookUrl, o job ID vai para o seu banco na hora do submit e o handler pega ele depois.
Como eu edito uma imagem existente com o Nano Banana Pro?
Use o slug google/nano-banana-pro/edit-image e acrescente um array image_urls ao objeto input, ao lado do seu prompt. As URLs precisam ser publicamente acessíveis, então uma URL assinada do seu armazenamento funciona, mas um caminho local não. Editar custa os mesmos $0.075 da geração.
Qual resolução devo pedir ao Nano Banana Pro?
Mande 2k. As duas primeiras configurações compartilham uma cobrança única e plana neste slug, então 1k é estritamente dominado — menos pixels, linha de fatura idêntica. Vá no 4k só quando você genuinamente precisar do teto de 4096×4096, já que ele dobra a cobrança para $0.15. Os três valores são em minúsculas.
Por que a API da E2X devolve preços como 75000?
Todo valor monetário na API é expresso em micro-centavos, onde 1.000.000 é igual a um dólar americano. Um 75000 num job do Nano Banana Pro é $0.075. Guarde o inteiro no armazenamento e divida só no ponto em que um humano lê, para o arredondamento nunca se acumular ao longo de um período de cobrança.