Автогенерация изображений: шаблоны, jobs и webhooks
Большинство ответов на этот вопрос — скриншот текстового поля. Набрать prompt, нажать кнопку, подождать, скачать, повторить. Это не автоматизация. Это вы, работающие руками, только быстрее.
Автоматически — значит без человека в контуре. Приходит строка данных, выходит изображение, и никто за этим не смотрел. Для этого нужны четыре части: шаблон prompt с дырками, данные, которыми дырки заполняются, очередь, которая принимает работу не блокируя, и надёжное место, куда класть результат. Мы держим API в середине этой конструкции, так что видим и пайплайны, которые люди строят, и те, что тихо ломаются через три недели после запуска.

То, чего вам не говорят первым: теперь это дёшево
Начнём с цифры, потому что она меняет то, что вы готовы построить.
Тысяча изображений через Nano Banana 2 Lite стоит $23.80. Это 1000 × $0.0238, по нашей цене на 26 августа 2026 года. Сто тысяч предметных кадров — настоящий каталог, каждый SKU, каждый цвет — это $2,380.
Те же объёмы по уровням, между которыми вы выбираете:
| Изображений | 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 |
Цены и условия продуктов со временем меняются; проверьте актуальные цены каждого провайдера, прежде чем принимать решение о покупке. Цены Google Batch или Flex не эквивалентны напрямую стандартному on-demand API request, поскольку планирование, доступность и условия обработки отличаются; поэтому они исключены из этого сравнения. Это ограниченное по охвату сравнение, а не утверждение, что E2X — самый дешёвый вариант в мире при любой конфигурации.
Большинство тех, кто спрашивает, как автоматизировать генерацию изображений, про себя уже оценили это как «слишком дорого, чтобы пробовать». Вопрос стоит $23.80. Стройте.
Шаг 1: перестаньте писать prompt, начните писать шаблон
Prompt, который вы набираете один раз, — это предложение. Prompt, который машина гоняет десять тысяч раз, — это шаблон со слотами, и в этой разнице вся работа.
Правило: всё, что меняется, идёт в слот, всё, что обязано остаться одинаковым, — в неподвижный текст. Ошибитесь с этой границей — и набор поплывёт: половина изображений на белом, половина на сером, потому что «фон» протёк в переменную часть.
TEMPLATE = (
"Студийная предметная фотография: {product_name}, цвет {colour}, "
"по центру на ровном тёпло-сером фоне, мягкий направленный рисующий свет "
"сверху слева, малая глубина резкости, редакционная каталожная подача, "
"без текста, без логотипов, без людей"
)
Два слота. Свет, фон, кадрирование и запреты заморожены. Именно эта замороженная половина делает 400 изображений одной съёмкой, а не 400 случайностями.
Держите четыре вещи вне переменной половины: медиум, направление света, фон и негативные инструкции. Это ваша визуальная идентичность. Если они гуляют от строки к строке, её у вас нет.

Само ремесло prompt — отдельная статья. Для автоматизации важна форма: неподвижная рамка, именованные дырки, никаких сюрпризов.
Шаг 2: данные и есть настоящий продукт
Шаблон тривиален. Работа живёт в данных, и обычно они у вас уже есть.
- Таблица товарного каталога. Строка на SKU, колонки под название, цвет, материал, категорию.
- Выгрузка постов из CMS, где заголовок и тема становятся слотами для картинки-шапки к каждой статье.
- Строки в базе вашего приложения — пользовательские объявления, страницы событий, рецепты.
- Таблица рекламных вариантов: шесть заголовков на четыре фона — это двадцать четыре изображения, и брифовать их руками никто не станет.
Приведите любой источник к списку словарей до того, как он коснётся API, и держите на каждой строке стабильный ID. Он нужен, чтобы сопоставлять результаты с записями, и его отсутствие бьёт ровно в тот момент, когда прилетает webhook.
Шаг 3: отправляйте, а не ждите
Наш API асинхронен намеренно. Вы делаете POST job, немедленно получаете job ID, а генерация происходит на нашей стороне.
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": "Студийная предметная фотография: настольная лампа из ореха, цвет матовый чёрный...",
"aspect_ratio": "1:1"
},
"webhookUrl": "https://your-app.example.com/hooks/e2x"
}'
Обходчик, превращающий таблицу в jobs, занимает около тридцати строк. Он читает CSV, форматирует шаблон по строке, отправляет и записывает job ID напротив ID строки, чтобы ничего не осиротело.
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 = (
"Студийная предметная фотография: {product_name}, цвет {colour}, "
"по центру на ровном тёпло-сером фоне, мягкий направленный рисующий свет "
"сверху слева, малая глубина резкости, редакционная каталожная подача, "
"без текста, без логотипов, без людей"
)
MAX_IMAGES = 500 # жёсткий потолок, см. раздел про стоимость
UNIT_COST = 0.0238 # Nano Banana 2 Lite, проверено 26 августа 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")
Обратите внимание, чего этот цикл не делает: не останавливается на упавшей строке. Батч из 500, где в строке 212 пустой цвет, должен дать вам 499 изображений и один залогированный пропуск, а не стектрейс в три часа ночи и ноль изображений.
Slug важнее, чем кажется. У google/nano-banana-2-lite/text-to-image есть суффикс возможности; text-слаг Nano Banana 2 — голый google/nano-banana-2, без него. Угадывание стоит вам 404 на пайплайне, который в остальном выглядит готовым. Каждая модель публикует список своих параметров в машиночитаемом файле спецификации.
Шаг 4: webhook вместо polling, и вот почему
Опрашивать можно. GET /v1/jobs/{JOB_ID} возвращает pending, processing, completed, failed или cancelled, и для разового скрипта это нормально.
Для пайплайна это неверный выбор, и причина не в изяществе. Polling связывает время жизни вашего процесса с самым медленным job в батче. Генерация на Lite идёт около 60 секунд на нашей стороне. Отправьте 500 — и опрашивающий процесс обязан оставаться живым, удерживая состояние, столько, сколько тянется хвост: через деплой, перезапуск контейнера, 15-минутный таймаут той serverless-площадки, на которой вы сидите. Когда он умирает, он умирает, держа единственную запись о том, какие jobs были в полёте.
Webhook переворачивает это. Вы отправляете и выходите. Позже прилетает POST с завершённым job, и вы обрабатываете ровно этот один job, без состояния.
// Express handler. Idempotent: the same jobId can arrive twice.
app.post("/hooks/e2x", express.json(), async (req, res) => {
res.status(200).end(); // acknowledge first, work after
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);
});
Опрашивайте, когда гоняете скрипт руками и смотрите на него. Берите webhook, когда не смотрит никто, — а это и есть определение автоматизации.
Шаг 5: URL вывода временный, и вот так ломаются пайплайны
Вот оно. Если вы всё остальное просматриваете по диагонали, этот раздел прочитайте дважды.
data.outputs[0].url — это URL доставки, а не хранилище. Сейчас он работает. Вечно работать не будет. Всё, что относится к нему как к постоянному — колонка product_image_url, поле в CMS, ссылка, отправленная клиенту письмом, — прекрасно рендерится на стейджинге и показывает иконки битых картинок через две недели.
Отказ мерзкий, потому что отложенный. В момент записи ничего не падает. Тесты проходят. Батч отчитывается о 500 успехах. Потом изображения начинают исчезать, начиная с самых старых записей, и к тому моменту, как кто-то заметит, перегенерировать их без повторного прогона всей задачи вы не сможете — потому что prompt тоже никто не сохранил.
Скачивайте байты. Кладите их в свой бакет. Храните свой 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}"
Раз уж вы там, сохраните рядом с изображением его prompt. Одна текстовая колонка — и это разница между «перегенерируй этот вариант» и «начинай заново».
Ещё две привычки, которые стоит завести с первого дня. Проверяйте, что скачивание вернуло байты изображения, а не страницу с ошибкой: 200 с HTML внутри с удовольствием запишется в ваш бакет. И ключуйте хранилище по ID вашей строки, а не по нашему job ID, чтобы перегенерация перезаписывала нужный объект, а не плодила сирот.
Шаг 6: что реально ломается на объёме
Маленькие батчи прячут всё. Вот что вылезает, когда вы переваливаете за несколько сотен.
Частичные батчи — норма. Какая-то доля любого большого прогона падает: prompt, задевший контентный фильтр, кривая строка, разовая ошибка выше по стеку. Считайте 497 из 500 успехом и заведите себе повтор в одну команду ровно по этим трём строкам.
Ретраям нужен потолок расходов. Цикл ретраев без ограничителя — это баг, который выставляет счёт. $0.0238 за выстрел звучит безобидно, пока залипший цикл не отработает сорок тысяч раз. Вбейте жёсткое число в код — строка MAX_IMAGES выше и есть самая дешёвая страховка.
Идемпотентность не опциональна. Webhook может быть доставлен больше одного раза. Обработчик, который небезопасно запускать дважды на одном job ID, даст вам дубли ассетов и дубли строк, и узнаете вы об этом в самую загруженную неделю.
Расходам нужен живой счётчик, а не месячный инвойс. Денежные значения в нашем API возвращаются в микроцентах: 1 000 000 равно $1. Суммируйте их по батчу и логируйте итог при закрытии. Пайплайн, который каждую ночь пишет «412 изображений, $9.81», — это пайплайн, который никому не надо аудировать.
Значения aspect ratio по умолчанию вас удивят. У Lite по умолчанию 1:1, а у Nano Banana 2 и Pro — 9:16. Зафиксируйте его в шаблоне, иначе половина каталога молча станет вертикальной.
Водяной знак едет вместе с файлом. Google впечатывает SynthID во всё, что производят его модели изображений. Он невидим, это не флаг, и он переживает всё, что ваш пайплайн потом делает с байтами. Тот, кто у вас отвечает за бренд или за юридическую сторону, должен согласовать ИИ-ассеты до того, как пайплайн появится, а не после того, как он записал в бакет 20 000 объектов.

На какую модель нацелить пайплайн
По умолчанию — Nano Banana 2 Lite, и уходите с неё, только когда что-то конкретное вас к этому вынудит.
Это арифметика, а не осторожность. За $0.0238 это самое дешёвое, что мы продаём из делающего изображения, она на целое поколение новее оригинальной Nano Banana, которую заменила, а на пайплайновых объёмах разрыв в цене складывается в единственную цифру, на которую смотрит финансовый отдел. У неё же четыре aspect ratio, которых нет больше нигде, — 1:4, 4:1, 1:8 и 8:1, ровно то, что нужно, если на выходе баннеры и вертикальные полосы. Одна особенность: параметра resolution нет вовсе, и отправка его — ошибка, а не молчаливый no-op.
Уводите работу с неё в этих четырёх случаях и ни в каких других:
- Вывод выше 1K. Lite — только 1K. Nano Banana 2 достаёт до 4K при базе $0.04, где 2K идёт ×1,5, а 4K — ×2.
- Читаемый текст в изображении. Товарные карточки с ценами, локализованная инфографика. Это живёт на Nano Banana Pro за $0.075, где 1K и 2K стоят одинаково — берите 2K, он бесплатный.
- Прозрачный фон. Вырезы и листы стикеров требуют
background: transparent, и GPT Image 2 — единственная модель у нас, которая делает это одним вызовом. Закладывайте $0.0525 на 1K, а не сырую каталожную цифру:qualityумножает на 7 на каждом уровне. - Одно и то же лицо или одна и та же бутылка в множестве кадров. Это задача про референсные изображения, а не про объём, и у неё своя статья про консистентность.
Для смешанной нагрузки гоняйте основную массу на Lite, а те 2%, которым нужен текст или точность, ответвляйте на Pro по колонке в ваших данных. Один пайплайн, две модели, цена Pro только там, где она что-то зарабатывает.
Разбор контракта submit и poll параметр за параметром — в руководстве по API Nano Banana Pro, а разбор по уровням — в сравнении моделей. Обе статьи исходят из того, что модель вы уже выбрали. Если нет — модели text-to-image перечислены с ценами, а каталог добавляет к ним видеосторону.
Всё целиком, по порядку
Шаблон со слотами. Данные со стабильными ID. Отправка jobs под жёстким потолком. Приём webhook. Скачивание байтов в собственное хранилище. Логирование отказов и расходов. Шесть шагов, ни один не сложный, и пропускают всегда пятый.
Частые вопросы
Как автоматически генерировать изображения?
Напишите свой prompt как шаблон с именованными слотами, заполните эти слоты из источника данных — товарного каталога или CSV — и отправьте каждый заполненный prompt асинхронным job в API изображений. Забирайте результат по webhook, а не опросом, затем скачивайте байты изображения в собственное хранилище. Ни одному звену этого цикла человек не нужен, и это делает его автоматическим, а не быстрой ручной работой.
Сколько стоит автоматически сгенерировать 1000 изображений?
По ценам E2X на 26 августа 2026 года 1000 изображений через Nano Banana 2 Lite — это $23.80, поскольку каждый request стоит $0.0238. Тот же объём — $40.00 на Nano Banana 2 и $75.00 на Nano Banana Pro. Более высокие разрешения умножаются сверху, но для стандартного вывода в 1K цифра за изображение и есть весь расчёт.
Что выбрать для jobs генерации изображений — webhook или polling?
Для всего, что работает без присмотра, — webhook. Polling привязывает время жизни вашего процесса к самому медленному job в батче, так что деплой или serverless-таймаут убивает опрашивающий процесс ровно тогда, когда он держит единственную запись о том, какие jobs ещё идут. Webhook позволяет отправить и выйти, а затем обрабатывать каждый завершённый job без состояния. Polling нормален для скрипта, за прогоном которого вы наблюдаете.
Почему мои сгенерированные изображения через какое-то время перестают загружаться?
Потому что вы сохранили URL вывода из API вместо самого изображения. Этот URL — временная доставка, а не хранилище, так что колонка с ним сначала рендерится правильно, а потом ломается. Скачивайте байты сразу по завершении job, пишите их в свой бакет, храните свой URL. Это самый частый способ отправить в прод сломанный пайплайн изображений.
Какая модель должна быть по умолчанию в автоматическом пайплайне?
Nano Banana 2 Lite, по $0.0238 за изображение. Это самая дешёвая модель изображений в нашем каталоге и на поколение новее оригинальной Nano Banana, а на батчевых объёмах цена перебивает любые другие соображения. Переходите на Nano Banana 2 только ради вывода выше 1K, на Nano Banana Pro — только ради читаемого текста в кадре, на GPT Image 2 — только ради прозрачного фона.
Что делать, когда часть изображений в батче падает?
Часть будет падать всегда, и пайплайн должен считать это нормой, а не фатальной ошибкой. Продолжайте отправку после отклонённой строки, логируйте ID отказов с их ошибками и постройте путь повтора, который прогоняет только упавшее. Ограничивайте число попыток — каждая из них реальные деньги.