Midjourney API на Python: генерация и сохранение
Создайте задачу, опрашивайте статус до завершения и сохраните одну PNG-сетку 2x2; ключ и тело запроса записываются на диск до POST.
На этой странице
- Коротко
- Какой API использует этот пример на Python?
- Что понадобится
- Тело запроса и параметры
- Вызов Midjourney API из Python
- Возвращаемая сетка изображений Midjourney
- Повторный запуск после тайм-аута или неудачного скачивания
- Ошибки и решения
- Стоимость и когда выбрать другой API изображений
- Частые вопросы
- Нужна ли подписка Midjourney для этого API?
- Почему одна задача Midjourney возвращает четыре изображения в одном файле?
- Что означает 409 idempotency_key_reused?
- Что будет, если скрипт не дождётся ответа при опросе?
Пример вызывает из Python сторонний API OmniaKey для Midjourney в три шага: создать задачу, опрашивать её статус до завершения и скачать PNG по подписанной ссылке на файл. Тестовый запуск интеграции 12 ноября 2026 года вернул изображение 2048x2048 за 197,1 секунды, и это изображение — сетка 2x2 с четырьмя вариантами. Скрипт ниже добавляет файл состояния, поэтому тайм-аут или неудачное скачивание не создают вторую платную задачу.
Коротко
- Отправьте
POST https://api.omniakey.com/v1/media/tasksсIdempotency-Key, затем опрашивайтеGET /v1/media/tasks/{id}и скачайтеassets[0].url. - Одна задача возвращает одну PNG-сетку с четырьмя изображениями, а не четыре отдельных файла.
- Midjourney в этом API работает только с текстом в изображение: без редактирования, референсного изображения, blend, upscale, vary, сдвига и выбора версии.
- Ключ выдаёт OmniaKey, он отделён от подписки Midjourney. Сохраните тело и ключ идемпотентности до POST, добавьте id задачи, когда API примет запрос, и оставьте файл состояния, чтобы продолжить позже. Локальный тайм-аут не отменяет задачу на сервере.
Какой API использует этот пример на Python?
Пример вызывает сторонний API OmniaKey для Midjourney. OmniaKey — не Midjourney, а ключ OmniaKey относится к отдельной учётной записи с собственным балансом. Подписка на сайте Midjourney — другой продукт, и ключ OmniaKey она не создаёт.
Страница модели Midjourney показывает модель и текущую цену на стороне OmniaKey. Дальше разобраны эта интеграция и жизненный цикл её задач.
Что понадобится
Нужны три вещи:
- Ключ API OmniaKey, экспортированный как
OMNIAKEY_API_KEY. - Python 3. Скрипт использует только стандартную библиотеку, поэтому SDK устанавливать не нужно.
midjourney, доступный для этого ключа/аккаунта. Если модели нет в списке ключа, проверьте доступ аккаунта до вызова создания.
export OMNIAKEY_API_KEY="your-key"
python3 midjourney_grid.py
Держите ключ вне скрипта и логов. Код читает его из окружения один раз.
Тело запроса и параметры
Midjourney принимает промпт и три параметра. Тип бота предустановлен на стороне OmniaKey, поэтому тело может не содержать версию модели и botType.
| Поле | Значение | Примечание |
|---|---|---|
input.prompt | Текст | Нужная сцена |
parameters.aspectRatio | 1:1 | Также 16:9, 9:16, 3:2, 2:3 |
parameters.quality | "1" | В документации используется строка "1" |
parameters.n | 1 | Должно быть 1 |
Задавайте форму через aspectRatio, а не через размер в пикселях. Поле записывается в camelCase: aspectRatio, никогда aspect_ratio. Используйте одно из пяти значений выше.
Тело для примера промпта выглядит так:
{
"model": "midjourney",
"type": "image",
"operation": "generate",
"input": {
"prompt": "A small red paper boat floating on a quiet blue lake at sunrise, cinematic lighting, elegant composition, no text"
},
"parameters": {"aspectRatio": "1:1", "quality": "1", "n": 1}
}
Вызов Midjourney API из Python
Сохраните его как midjourney_grid.py и запустите. Первый запуск записывает midjourney-task.json до POST, затем добавляет id задачи, когда OmniaKey принимает запрос.
import hashlib
import json
import os
import sys
import time
import urllib.error
import urllib.request
import uuid
from pathlib import Path
API = "https://api.omniakey.com/v1"
KEY = os.environ["OMNIAKEY_API_KEY"]
STATE = Path("midjourney-task.json")
OUT = Path("midjourney-grid.png")
PROMPT = ("A small red paper boat floating on a quiet blue lake at sunrise, "
"cinematic lighting, elegant composition, no text")
def save(state):
tmp = STATE.with_suffix(".tmp")
tmp.write_text(json.dumps(state, indent=2))
tmp.replace(STATE)
def read_json(response):
raw = response.read()
try:
return json.loads(raw)
except ValueError:
return {"raw": raw[:300].decode("utf-8", "replace")}
def call(method, path, body=None, headers=None):
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(
API + path, data=data, method=method,
headers={"Authorization": f"Bearer {KEY}", **(headers or {})})
if body is not None:
req.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req, timeout=60) as response:
return response.status, read_json(response)
except urllib.error.HTTPError as error:
return error.code, read_json(error)
except (urllib.error.URLError, TimeoutError) as error:
return None, {"error": str(error)}
def main():
state = json.loads(STATE.read_text()) if STATE.exists() else None
if state is None:
body = {
"model": "midjourney", "type": "image", "operation": "generate",
"input": {"prompt": PROMPT},
"parameters": {"aspectRatio": "1:1", "quality": "1", "n": 1},
}
state = {"idempotency_key": str(uuid.uuid4()), "body": body,
"task_id": None, "status": "creating"}
save(state) # no API key here; write before POST
elif state["body"]["input"]["prompt"] != PROMPT:
sys.exit("The state file holds a different prompt. Move it aside to start a new job.")
if OUT.exists() and state.get("status") == "downloaded":
saved_sha = state.get("download_sha")
if not saved_sha or hashlib.sha256(OUT.read_bytes()).hexdigest() == saved_sha:
print("already saved", OUT)
return
if not state["task_id"]:
code, task = call("POST", "/media/tasks", state["body"],
{"Idempotency-Key": state["idempotency_key"]})
if code is None:
sys.exit(f"Create not confirmed: {task['error']}. Rerun to retry the same key and body.")
if code == 409:
sys.exit("409 idempotency_key_reused: same key, different body. Restore the saved body.")
if code == 503:
sys.exit("503 no_available_channel: check model access for this key/account; "
"contact support with the request id if it persists.")
if code not in (200, 202) or "id" not in task:
sys.exit(f"Create failed: HTTP {code} {task}")
state.update(task_id=task["id"], status=task["status"])
save(state)
deadline = time.monotonic() + 900
while True:
code, task = call("GET", f"/media/tasks/{state['task_id']}")
if code is None:
sys.exit(f"Polling stopped: {task['error']}. The job is still saved; rerun this script.")
if code != 200 or "status" not in task:
sys.exit(f"Poll failed: HTTP {code} {task}")
state["status"] = task["status"]
save(state)
if task["status"] in ("failed", "canceled"):
sys.exit(f"Job {task['status']}: {task.get('error')}")
if task["status"] == "succeeded":
break
if time.monotonic() > deadline:
sys.exit(f"Still {task['status']} after 900 s. Rerun with the same state file.")
time.sleep(min(8, 2 + task.get("progress", {}).get("percent", 0) // 20))
asset = task["assets"][0]
if asset.get("mime_type") != "image/png":
sys.exit(f"Expected a PNG grid, got {asset.get('mime_type')}")
try:
with urllib.request.urlopen(asset["url"], timeout=120) as response:
data = response.read() # signed URL: no Authorization header
except (urllib.error.HTTPError, urllib.error.URLError, TimeoutError) as error:
sys.exit(f"Download failed: {error}. Rerun to get a fresh signed URL and retry.")
digest = hashlib.sha256(data).hexdigest()
if asset.get("sha256") and digest != asset["sha256"]:
sys.exit("SHA-256 mismatch. Rerun to download the asset again.")
part = OUT.with_suffix(".part")
part.write_bytes(data)
part.replace(OUT)
state.update(status="downloaded", download_sha=digest)
save(state)
print("saved", OUT, len(data), "bytes")
if __name__ == "__main__":
main()
Формы запроса и ответа взяты из тестового запуска 12 ноября; состояние для перезапуска — добавление здесь, а его ветки жизненного цикла проверены офлайн на имитированных ответах.
Файл состояния хранит то, что нужно для продолжения: JSON-тело и UUID, используемый как ключ идемпотентности. Ключа API там нет. Файл записывается до POST, поэтому оборванное соединение оставляет оба значения на диске для восстановления.
Вызов создания может вернуть два кода успеха. 202 принимает новую задачу. 200 означает, что OmniaKey уже видел тот же ключ и то же тело и возвращает исходную задачу; продолжайте опрашивать эту задачу. Повторно отправляйте то же тело и ключ, только если результат создания неизвестен после сбоя соединения. Для изменённого тела документация указывает 409 idempotency_key_reused.
Управляйте циклом опроса по status. Процент приблизительный и может оставаться на 0, пока задача выполняется. Останавливайтесь на succeeded, failed или canceled. Документация OmniaKey о жизненном цикле медиазадач (проверена 12 ноября 2026 года) описывает эти состояния.
Лимит опроса локальный. Когда он истекает, задача всё ещё выполняется или стоит в очереди; повторный запуск того же скрипта читает сохранённый id и продолжает опрос. Локальный тайм-аут не отменяет задачу. Для отмены есть отдельный endpoint.
Возвращаемая сетка изображений Midjourney
У успешной задачи один файл: PNG-сетка. Вот образец из тестового запуска 12 ноября 2026 года:

Ответ ниже сокращён: ссылка скрыта, а expires_at — историческое значение. В нём добавлены нужные поля файла:
{
"id": "task_DbCWJPfPL1jyhSD38IihNHiiFQao8hjV",
"status": "succeeded",
"progress": {"percent": 100, "stage": "completed"},
"assets": [
{
"kind": "image",
"mime_type": "image/png",
"width": 2048,
"height": 2048,
"byte_size": 5834301,
"sha256": "f503cef038d15fa150b36b5fe7c41874febad3aa1167698d790781e47a4a10b9",
"expires_at": "2026-11-12T04:16:41Z",
"url": "https://…redacted-signed-url…"
}
]
}
Читайте width и height из файла, а не задавайте 2048 жёстко. Квадрат 2048x2048 и 197,1 секунды — один образец; относитесь к ним именно так. Все четыре варианта находятся в одном PNG, поэтому разделите сетку после скачивания, если приложению нужны отдельные изображения.
url подписана и живёт недолго, около часа. Запись задачи и изображение хранятся 24 часа после создания, согласно документации API изображений OmniaKey (проверена 12 ноября 2026 года). Скачивайте до истечения ссылки. Если она истекла, опросите id задачи ещё раз, чтобы получить новую подписанную ссылку.
Скачивайте файл по ссылке обычным GET без заголовка Authorization. Подпись находится в самой ссылке. Скрипт также сравнивает скачанные байты с sha256 задачи, поэтому обрезанный файл обнаруживается до перезаписи хорошей копии.
Повторный запуск после тайм-аута или неудачного скачивания
Запустите ту же команду и сохраните задачу. Скрипт читает midjourney-task.json и выбирает самый короткий безопасный путь:
- Создание не подтверждено: он снова отправляет POST с тем же телом и тем же ключом идемпотентности.
- Id задачи есть, задача ещё выполняется: он опрашивает существующий id.
- Задача завершилась, но PNG отсутствует: он один раз опрашивает задачу ради новой подписанной ссылки и скачивает снова.
- PNG уже сохранён и совпадает с записанным SHA-256: он завершается без нового запроса. Повреждённый файл опрашивается и скачивается заново.
Это работает в пределах 24-часового хранения. Тот же ключ с другим промптом или параметрами даёт 409. Скрипт отказывается от файла состояния, если промпт больше не совпадает; уберите файл, если меняете параметры.
Ошибки и решения
Сбои ниже следуют из правил задач и идемпотентности API. Исправьте причину и, где применимо, запустите снова с тем же файлом состояния.
| Ответ | Код | Что делать |
|---|---|---|
| 400 | missing_idempotency_key | Добавьте заголовок; держите его короче 255 символов |
| 409 | idempotency_key_reused | Отправьте исходное тело или начните новую задачу с новым ключом |
| 503 | no_available_channel | Проверьте доступ для этого ключа или аккаунта; если не проходит, обратитесь в поддержку |
failed | error.code, error.message | Прочитайте ошибку задачи; деньги за неудачные задачи возвращаются |
canceled | — | Задача остановлена; создайте новую, если она ещё нужна |
503 no_available_channel не означает, что у ключа нет разрешения на модель. Две отправки с одним ключом из этого пакета вернули 503 и не создали задачу; другой ключ успешно сработал в более раннем тестовом запуске того же дня. Причина неизвестна: это может быть доступность ключа/аккаунта или доступность вышестоящего канала. Проверьте, доступен ли midjourney вашему текущему ключу/аккаунту. Если 503 повторяется, отправьте поддержке тело ответа и id запроса.
200 при создании — это повтор исходной задачи. Терминальными сбоями считаются только failed и canceled; если остановить скрипт, задача продолжит выполняться.
Стоимость и когда выбрать другой API изображений
OmniaKey берёт плату один раз за успешную задачу, а деньги за неудачные и отменённые задачи возвращаются автоматически. Текущая цена указана на странице модели Midjourney; проверьте её перед оценкой пакета.
Midjourney подходит, когда нужно выбрать один из четырёх вариантов в одной сетке. Эта интеграция возвращает такую сетку из текста в изображение; если нужна одна картинка за вызов или редактирование с сохранением исходной сцены, посмотрите GPT Image 2.5 API или Nano Banana Pro API.
Если проще ввести промпт, чем писать код, AI Image Generator запускает модели изображений в браузере.
Частые вопросы
Нужна ли подписка Midjourney для этого API?
Нет. Ключ выдаёт OmniaKey, а вызовы идут в сторонний API OmniaKey. OmniaKey — не Midjourney; подписка на сайте Midjourney — другой продукт, и ключ OmniaKey она не создаёт.
Почему одна задача Midjourney возвращает четыре изображения в одном файле?
Midjourney возвращает сетку 2x2 с четырьмя вариантами одним PNG. Все четыре используют этот файл, а upscale и vary в этой интеграции недоступны, поэтому разделите сетку сами после скачивания, если нужны отдельные файлы.
Что означает 409 idempotency_key_reused?
Вы отправили ключ идемпотентности, который у OmniaKey уже есть, но тело запроса изменилось. Отправьте исходное тело или начните новую задачу с новым ключом. Обычная причина — повторное использование ключа с другим промптом.
Что будет, если скрипт не дождётся ответа при опросе?
Задача продолжит выполняться. Id задачи лежит в midjourney-task.json, поэтому повторный запуск того же скрипта опросит этот id или скачает готовый файл. Локальный тайм-аут оставляет задачу живой, а оплату — в силе.
Модели в этой статье
Похожие статьи
Nano Banana Pro API: инструкция на Python, редактирование и цена
Один POST, один опрос, одно скачивание — скрипт для Nano Banana Pro, который работает.
GPT Image 2.5 API: генерация и редактирование изображений на Python
Один POST создаёт задачу, один GET читает результат. Ниже — код запроса, размеры из запусков 29 октября 2026 года и одно редактирование до и после.
GPT Image 2.6: обзор, GIF, SVG, цена и кому подходит
Что нового в GPT Image 2.6: проверили на своём GIF, трёх постерах и правке фото.

