Как вызывать API Nano Banana Pro: полное руководство
Это вся интеграция Nano Banana Pro на API E2X от начала до конца. Получить ключ, отправить job, прочитать response, дождаться его как следует, обработать способы, которыми он может упасть, и положить байты туда, где они будут лежать и завтра.
Последний шаг — тот, на котором люди попадаются. Перейдите сразу к нему, если вы уже генерируете изображения и просто хотите знать, почему ваши пропали.

Подходит ли Pro как уровень под вашу работу — другой вопрос, и на него мы ответили отдельно, в статье про цену и скорость. Коротко: $0.075 за request, около 30 секунд, оправданно, если ваши изображения несут читаемый текст или совмещают референсы с разными ролями. Эта страница исходит из того, что вы уже решили.
Что нужно до первого request
Четыре вещи, и три из них по одной строке.
- API-ключ. Создайте его в своём аккаунте E2X. Всё дальше отправляет его как
Authorization: Bearer $E2X_API_KEY. Держите его на сервере. Ключ в браузерном JavaScript — это ключ, который уже тратит кто-то другой. - Базовый URL —
https://api.e2x.ai/v1для каждой модели в каталоге. - Правильный slug. Генерация —
google/nano-banana-pro/text-to-image. Редактирование —google/nano-banana-pro/edit-image. Эти строки идут в тело request дословно, нечёткого совпадения по ним нет. - Prompt, который стоит отправлять. Pro вознаграждает конкретность сильнее flash-уровней, потому что у неё больше пространства ею воспользоваться. Есть отдельное руководство по prompt, если хотите глубже, чем «кот, но кинематографично».
Контракт асинхронный. Вы отправляете job, получаете ID, а изображение приходит позже. Синхронного endpoint, который блокируется до готовности пикселей, нет, и, учитывая, что Pro занимает около 30 секунд, он вам бы и не понравился.
Ваш первый 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": "Меловое меню, написанное от руки, за мраморной кофейной стойкой, тёплый послеполуденный свет из окна слева, малая глубина резкости",
"aspect_ratio": "16:9",
"resolution": "2k"
}
}'
То же самое на 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"]
Оба примера фиксируют aspect_ratio и resolution намеренно, и оба значения по умолчанию стоит понять, прежде чем их выбрасывать.
aspect_ratio по умолчанию — 9:16. Не 1:1. Если вы его не укажете, каждое сгенерированное изображение будет вертикальным, всегда, и вы этого не заметите, пока широкий слот в вашем макете не начнёт выглядеть неправильно. Это подставляет больше интеграций, чем любое другое поле нашего API. Фиксируйте его.
Значения resolution в нижнем регистре. 1k, 2k, 4k. 2K заглавными — другая строка, и API вам об этом скажет.
Считайте 1k мёртвым значением на этом slug. Первые две настройки сидят на одном плоском списании — $0.075 за любую, проверено 26 августа 2026 года, — так что меньшая просто возвращает меньше пикселей за ту же строку в счёте. Цифру двигает только 4k: $0.15, потолок 4096×4096.

Что приходит обратно
Вызов submit возвращается сразу, с конвертом job. Нужное вам поле — data.jobId:
{
"success": true,
"data": {
"jobId": "job_8Kd2mQvXpL",
"status": "pending"
}
}
Статус идёт pending → processing → completed либо садится на один из двух терминальных отказов, failed и cancelled. Завершённый job несёт результат в data.outputs[0].url, а упавший — причину в data.error.message.
Одна деталь, которую стоит усвоить до того, как вы напишете хоть какой-то биллинговый код: любое денежное значение, которое возвращает API, выражено в микроцентах. Один миллион равен одному доллару. Поэтому request к Nano Banana Pro приходит как 75000, а не 0.075. Делите на 1 000 000 на слое отображения и больше нигде, а поделённое число никогда не сохраняйте.
Polling без долбёжки
Топорная версия работает:
curl https://api.e2x.ai/v1/jobs/job_8Kd2mQvXpL \
-H "Authorization: Bearer $E2X_API_KEY"
Оберните это в цикл с фиксированной паузой в две секунды — и у вас работающая интеграция. Конкретно для Nano Banana Pro фиксированный интервал честно приемлем: request идёт около 30 секунд, значит, вы делаете примерно пятнадцать вызовов статуса и останавливаетесь. Такого трафика никому не хватит, чтобы обеспокоиться.
Backoff всё же лучше, по одной причине, к вежливости отношения не имеющей. Фиксированные интервалы прячут разброс. Если job занимает 90 секунд вместо 30, фиксированный цикл продолжает опрашивать с той же частотой, и в логах это выглядит идентично здоровому прогону. Растущий backoff делает медленный job видимо медленным.
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")
Две вещи этот цикл делает, а наивный обычно нет. У него есть жёсткий дедлайн, так что залипший job поднимает исключение, а не крутится, пока процесс не убьют. И он одинаково обходится со всеми тремя терминальными состояниями на транспортном уровне, возвращая payload и оставляя вызывающему решать, что значит отказ. Логика повторов живёт над этой функцией, а не внутри неё.

Webhooks и почему стоит на них перейти
Передайте webhookUrl в теле submit — и мы позвоним вам, когда job дойдёт до терминального состояния. Никакого цикла опроса вовсе.
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": "Архитектурный макет пешеходного моста на чертёжном столе, северный свет",
"aspect_ratio": "3:2",
"resolution": "2k"
},
"webhookUrl": "https://your-app.example.com/hooks/e2x"
}'
Polling — правильный первый шаг, потому что его можно проверить из терминала за десять секунд. Это неправильное устойчивое состояние, и причина арифметическая. По одному изображению за раз polling стоит вам одного цикла. Двести изображений в батче — и polling стоит вам двухсот параллельных циклов, каждый из которых держит соединение полминуты, в процессе, который теперь нельзя перезапустить, не потеряв след всех job в полёте.
Webhooks делают работу перезапускаемой. Job ID уходит в вашу базу при отправке, обработчик обновляет строку, когда мы позвоним, и деплой посреди этого не меняет ничего. Если вы строите пайплайн генерации, а не скрипт, строить надо эту версию, и она сочетается с приёмами из нашей статьи про автоматизацию генерации изображений от начала до конца.
Две операционные заметки. Ваш endpoint должен быть достижим из публичного интернета, так что URL на localhost во время разработки молча не сработает никогда — используйте туннель. И относитесь к webhook как к уведомлению, а не как к источнику истины: внутри обработчика запросите job по ID, прежде чем действовать.
Редактирование изображения вместо генерации
Тот же конверт, другой slug, одно дополнительное поле. Edit-endpoint берёт image_urls рядом с prompt и стоит те же $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": "Замени фон на мягкую серую студийную циклораму, свет на товаре сохрани ровно как есть",
"image_urls": ["https://your-cdn.example.com/source/bottle.jpg"],
"aspect_ratio": "1:1",
"resolution": "2k"
}
}'
URL, которые вы передаёте, должны быть публично загружаемыми. Подписанная ссылка из вашего хранилища работает; путь на вашем ноутбуке — нет.
Nano Banana Pro принимает до четырнадцати референсных изображений и, в отличие от остальных уровней, разделяет их по ролям — до 5 на идентичность персонажа, до 6 на точность объекта, до 3 на стиль. Именно это разделение ролей и есть причина вообще быть на этом уровне, а точные имена полей для референсов с ролями задокументированы по каждой модели в машиночитаемом файле спецификации, который является авторитетом, если наша документация и эта статья когда-нибудь разойдутся. Конкретно по процессу консистентности персонажа мы идём глубже в руководстве по консистентной генерации.
Все параметры и их значения по умолчанию
| Поле | Куда идёт | Допустимые значения | По умолчанию |
|---|---|---|---|
model | корень тела | google/nano-banana-pro/text-to-image или google/nano-banana-pro/edit-image | обязательно |
input.prompt | внутри input | строка | обязательно |
input.aspect_ratio | внутри 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 | внутри input | 1k, 2k, 4k | фиксируйте явно |
input.image_urls | внутри input, только edit-slug | массив публично достижимых URL | обязательно при edit |
webhookUrl | корень тела | публичный HTTPS-endpoint | нет, вместо него polling |
Две строки, которые стоят людям денег, — это aspect_ratio и resolution. Всё остальное ведёт себя так, как вы и предположили бы.
Когда job не завершается
Три поверхности отказа, и обрабатывать их надо по-разному.
Падает сам вызов submit. Это HTTP-ошибка до того, как job вообще появился: плохой ключ, кривое тело, неизвестный slug модели. В очередь ничего не поставлено и денег не списано. Чините request; повтор без изменений упадёт точно так же.
Job доходит до failed. Job существовал, а модель изображения не выдала. Читайте причину в data.error.message — обычно это отказ по контентной политике или кривой вход, например недостижимый URL в image_urls. Слепой повтор отказа по политике падает так же; повтор после разовой ошибки выше по стеку обычно проходит. Логируйте сообщение, а не только статус.
Job доходит до cancelled. Терминально и не баг. На уровне кода обходитесь с ним ровно как с failed: строка закрыта, вывода не будет.
Ваш цикл ожидания истекает по времени. Это не состояние job. Это значит, что job ещё идёт, а ваше терпение кончилось. Job ID остаётся действительным, так что запишите его и проверьте позже, а не отправляйте заново — иначе вы заплатите за одно и то же изображение дважды.
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"]
Скачайте вывод, пока он не протух
Именно эта часть отправляет в прод битые картинки через неделю после запуска, так что ей отдан отдельный раздел.
URL в data.outputs[0].url временный. Это URL доставки, а не хостинг. Если вы запишете эту строку в колонку базы под названием image_url и отрендерите её на странице товара, страница будет работать на стейджинге, работать на ревью, работать в день запуска, а потом тихо превратится в иконки битых изображений, как только объект состарится.
Лечение в один шаг, и оно не опционально. Заберите байты, положите в собственное хранилище, храните свой 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
Или из шелла:
curl -sL "$OUTPUT_URL" -o ./out/menu-board.jpg
Делайте скачивание внутри той же единицы работы, которая обработала завершение. Не по cron через час, не лениво при первом просмотре страницы. Окно достаточно щедрое, чтобы задержка сошла вам с рук на тестах, и достаточно узкое, чтобы под нагрузкой не сошла.

Всё целиком, одним скриптом
Всё вышеописанное, собранное вместе. Задайте E2X_API_KEY и запустите.
#!/usr/bin/env python3
"""Сгенерировать одно изображение через Nano Banana Pro и сохранить локально."""
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(
"Меловое меню, написанное от руки, за мраморной кофейной стойкой, "
"тёплый послеполуденный свет из окна слева, малая глубина резкости"
)
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()
Поменяйте slug на google/nano-banana-pro/edit-image, добавьте image_urls — и тот же скрипт редактирует вместо генерации. Поменяйте его на Nano Banana 2 или Nano Banana 2 Lite — и он всё ещё работает, потому что конверт идентичен у всего в категории text-to-image и всего в image-to-image. Различаются только поля input, а на какую модель его нацелить, разбирает статья со сравнением моделей.
Последнее, и это не про код. Каждое изображение от каждой модели Google несёт водяной знак SynthID, и отключить его не может ни один провайдер. Закройте это с тем, кто подписывает договор, до того, как будете строить сверху.
Частые вопросы
Как вызвать API Nano Banana Pro?
Отправьте POST на https://api.e2x.ai/v1/jobs/submit с bearer-токеном, slug модели google/nano-banana-pro/text-to-image и объектом input, содержащим ваш prompt. В response вернётся data.jobId. Опрашивайте https://api.e2x.ai/v1/jobs/{id} или передайте webhookUrl в теле submit, затем прочитайте готовое изображение из data.outputs[0].url.
Какое aspect ratio по умолчанию у Nano Banana Pro?
9:16, то есть вертикаль. Это удивляет почти всех, потому что большинство API изображений по умолчанию дают квадрат. Если вы не зададите aspect_ratio явно, каждое изображение выйдет вертикальным. Модель принимает 9:16, 16:9, 1:1, 2:3, 3:2, 21:9, 3:4, 4:3, 4:5 и 5:4.
Почему URL моего изображения от Nano Banana Pro битый?
Потому что возвращаемый нами URL вывода — временная доставка, а не постоянный хостинг. Любой пайплайн, сохраняющий наш URL в базу и рендерящий его позже, покажет битые изображения, как только объект протухнет. Скачивайте байты на том же шаге, который обрабатывает завершение job, и храните их в собственном бакете.
Что использовать с API E2X — polling или webhooks?
Опрашивайте, пока строите, потому что это проверяется из терминала. Переходите на webhooks для всего, что работает в проде. Job к Nano Banana Pro занимает около 30 секунд, так что батч из двухсот означает двести параллельных циклов опроса, которые все теряют состояние на следующем деплое. С webhookUrl job ID уходит в вашу базу в момент отправки, а обработчик подхватывает его позже.
Как отредактировать существующее изображение через Nano Banana Pro?
Возьмите slug google/nano-banana-pro/edit-image и добавьте в объект input массив image_urls рядом с вашим prompt. URL должны быть публично загружаемыми, так что подписанная ссылка из вашего хранилища работает, а локальный путь — нет. Редактирование стоит те же $0.075, что и генерация.
Какое разрешение запрашивать у Nano Banana Pro?
Шлите 2k. Первые две настройки делят на этом slug одно плоское списание, так что 1k строго проигрывает: меньше пикселей, та же строка в счёте. Тянитесь к 4k, только когда вам действительно нужен потолок 4096×4096, поскольку он удваивает списание до $0.15. Все три значения в нижнем регистре.
Почему API E2X возвращает цены вроде 75000?
Каждое денежное значение в API выражено в микроцентах, где 1 000 000 равен одному доллару США. 75000 на job к Nano Banana Pro — это $0.075. Держите в хранилище целое число и делите только там, где его читает человек, чтобы округление не накапливалось за расчётный период.