API de Midjourney en Python: genera y guarda imágenes
Crea la tarea, consulta hasta que termine y guarda una cuadrícula PNG 2x2; la clave y el cuerpo quedan en disco antes del POST.
En esta página
- Respuesta corta
- ¿Qué API usa este ejemplo de Python?
- Qué necesitas
- Cuerpo de la solicitud y parámetros
- Llamar a la API de Midjourney desde Python
- La cuadrícula de imágenes de Midjourney que devuelve la API
- Volver a ejecutar tras un tiempo de espera o una descarga fallida
- Errores y soluciones
- Coste y cuándo usar otra API de imágenes
- Preguntas frecuentes
- ¿Necesito una suscripción de Midjourney para usar esta API?
- ¿Por qué una tarea de Midjourney devuelve cuatro imágenes en un solo archivo?
- ¿Qué significa 409 idempotency_key_reused?
- ¿Qué pasa si mi script agota el tiempo mientras consulta?
Este ejemplo llama desde Python a la API de Midjourney de OmniaKey, un servicio de terceros, en tres pasos: crear la tarea, consultar su estado hasta que termine y descargar el PNG desde la URL firmada del recurso. Una prueba de integración del 12 de noviembre de 2026 devolvió una imagen de 2048x2048 en 197,1 segundos, y esa imagen es una cuadrícula 2x2 con cuatro candidatos. El script de abajo añade un archivo de estado, así que un tiempo de espera agotado o una descarga fallida no crean un segundo trabajo de pago.
Respuesta corta
- Envía
POST https://api.omniakey.com/v1/media/taskscon unIdempotency-Key, luego consultaGET /v1/media/tasks/{id}y descargaassets[0].url. - Una tarea devuelve una cuadrícula PNG con cuatro imágenes, no cuatro archivos separados.
- Midjourney en esta API solo hace texto a imagen: sin edición, imagen de referencia, blend, upscale, vary, desplazamiento ni selector de versión.
- La clave viene de OmniaKey y es independiente de una suscripción de Midjourney. Guarda el cuerpo y la clave de idempotencia antes del POST, añade el id de la tarea cuando la API la acepte y conserva el archivo de estado para reanudar más tarde. Un tiempo de espera local no cancela la tarea del servidor.
¿Qué API usa este ejemplo de Python?
El ejemplo llama a la API de terceros de OmniaKey para Midjourney. OmniaKey no es Midjourney, y una clave de OmniaKey es una cuenta aparte con su propio saldo. Una suscripción del sitio web de Midjourney es un producto distinto y no crea una clave de OmniaKey.
La página del modelo Midjourney muestra el modelo y la tarifa actual del lado de OmniaKey. El resto del recorrido cubre esta integración y el ciclo de vida de sus tareas.
Qué necesitas
Necesitas tres cosas:
- Una clave de API de OmniaKey, exportada como
OMNIAKEY_API_KEY. - Python 3. El script usa solo la biblioteca estándar, así que no hay SDK que instalar.
midjourneydisponible para esa clave/cuenta. Si el modelo no aparece en la lista de la clave, revisa el acceso de tu cuenta antes de ejecutar la llamada de creación.
export OMNIAKEY_API_KEY="your-key"
python3 midjourney_grid.py
Mantén la clave fuera del script y de los registros. El código la lee una vez del entorno.
Cuerpo de la solicitud y parámetros
Midjourney recibe un prompt y tres parámetros. El tipo de bot está preconfigurado en OmniaKey, así que el cuerpo puede omitir la versión del modelo y botType.
| Campo | Valor | Notas |
|---|---|---|
input.prompt | Texto | La escena que quieres |
parameters.aspectRatio | 1:1 | También 16:9, 9:16, 3:2, 2:3 |
parameters.quality | "1" | La documentación usa la cadena "1" |
parameters.n | 1 | Debe ser 1 |
Define la forma con aspectRatio, no con un tamaño en píxeles. El campo va en camelCase: aspectRatio, nunca aspect_ratio. Usa uno de los cinco valores indicados.
El cuerpo para el prompt de muestra es así:
{
"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}
}
Llamar a la API de Midjourney desde Python
Guarda esto como midjourney_grid.py y ejecútalo. La primera ejecución escribe midjourney-task.json antes del POST y añade el id de la tarea cuando OmniaKey acepta la solicitud.
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()
Las formas de la solicitud y la respuesta vienen de la prueba de integración del 12 de noviembre; el estado de reinicio es la novedad aquí, y sus pasos del ciclo de vida se comprobaron sin conexión con respuestas simuladas.
El archivo de estado conserva lo necesario para reanudar: el cuerpo JSON y el UUID usado como clave de idempotencia. No guarda ninguna clave de API. Se escribe antes del POST, así que una conexión interrumpida deja ambos valores en disco para recuperarlos.
La llamada de creación puede devolver dos códigos de éxito. Un 202 acepta un trabajo nuevo. Un 200 significa que OmniaKey ya vio la misma clave y el mismo cuerpo y devuelve la tarea original; sigue consultando esa tarea. Vuelve a enviar el mismo cuerpo y la misma clave solo cuando el resultado de la creación sea desconocido tras un fallo de conexión. La documentación indica 409 idempotency_key_reused para un cuerpo modificado.
Controla el bucle de consulta con status. El porcentaje es orientativo y puede quedarse en 0 mientras el trabajo avanza. Detente en succeeded, failed o canceled. La documentación del ciclo de vida de tareas multimedia de OmniaKey (revisada el 12 de noviembre de 2026) describe esos estados.
El límite de consultas es local. Cuando vence, el trabajo sigue en ejecución o en cola; al volver a ejecutar el mismo script se lee el id guardado y se sigue consultando. Un tiempo de espera local no es una cancelación. La cancelación se hace con otro endpoint.
La cuadrícula de imágenes de Midjourney que devuelve la API
Una tarea exitosa tiene un solo recurso: una cuadrícula PNG. Esta es la muestra de la prueba de integración del 12 de noviembre de 2026:

La respuesta de abajo está abreviada: la URL está oculta y expires_at es histórico. Añade los campos del recurso que necesitas:
{
"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…"
}
]
}
Lee width y height del recurso en lugar de fijar 2048. El cuadrado de 2048x2048 y los 197,1 segundos son una muestra; trátalos como tal. Los cuatro candidatos comparten un PNG, así que divide la cuadrícula tras la descarga si tu aplicación necesita imágenes sueltas.
La url está firmada y dura poco, alrededor de una hora. El registro de la tarea y la imagen se conservan 24 horas después de la creación, según la documentación de la API de imágenes de OmniaKey (revisada el 12 de noviembre de 2026). Descarga antes de que caduque la URL. Si ya caducó, consulta otra vez el id de la tarea para obtener una URL firmada nueva.
Descarga la URL del recurso con un GET normal y sin cabecera Authorization. La firma está en la URL. El script también compara los bytes descargados con el sha256 de la tarea, lo que detecta un archivo truncado antes de que sobrescribas tu copia buena.
Volver a ejecutar tras un tiempo de espera o una descarga fallida
Ejecuta el mismo comando y conserva el trabajo. El script lee midjourney-task.json y toma el camino seguro más corto:
- La creación no se confirmó: hace POST del mismo cuerpo con la misma clave de idempotencia.
- Existe el id y el trabajo sigue en ejecución: consulta el id existente.
- El trabajo terminó pero falta el PNG: consulta una vez para obtener una URL firmada nueva y descarga de nuevo.
- El PNG ya está guardado y coincide con su SHA-256 registrado: sale sin otra solicitud. Un archivo dañado se consulta y se descarga otra vez.
Esto funciona dentro de la ventana de conservación de 24 horas. La misma clave con un prompt o unos parámetros distintos produce un 409. El script rechaza un archivo de estado cuyo prompt ya no coincide; mueve el archivo si cambias los parámetros.
Errores y soluciones
Los fallos de abajo vienen de las reglas de tareas e idempotencia de la API. Corrige la causa y vuelve a ejecutar con el mismo archivo de estado cuando corresponda.
| Respuesta | Código | Qué hacer |
|---|---|---|
| 400 | missing_idempotency_key | Añade la cabecera; mantenla por debajo de 255 caracteres |
| 409 | idempotency_key_reused | Envía el cuerpo original o empieza un trabajo nuevo con otra clave |
| 503 | no_available_channel | Comprueba el acceso de esta clave/cuenta; contacta con soporte si persiste |
failed | error.code, error.message | Lee el error de la tarea; los trabajos fallidos se reembolsan |
canceled | — | El trabajo se detuvo; crea otro si aún lo necesitas |
Un 503 no_available_channel no demuestra que a una clave le falte permiso para el modelo. Dos envíos con una clave usada en este lote devolvieron 503 y no crearon ninguna tarea; otra clave tuvo éxito en una prueba de integración anterior del mismo día. La causa se desconoce: puede ser la disponibilidad de la clave/cuenta o la del canal upstream. Confirma que midjourney está disponible para tu clave/cuenta actual. Si el 503 persiste, envía a soporte el cuerpo de la respuesta y el id de la solicitud.
Un 200 al crear es una repetición de la tarea original. Solo failed y canceled son fallos terminales; si detienes el script, el trabajo sigue.
Coste y cuándo usar otra API de imágenes
OmniaKey cobra una vez por trabajo exitoso, y los trabajos fallidos o cancelados se reembolsan automáticamente. La tarifa actual está en la página del modelo Midjourney; revísala antes de calcular un lote.
Midjourney encaja para elegir entre cuatro variaciones en una cuadrícula. Esta integración devuelve esa cuadrícula de texto a imagen; para una imagen por llamada o una edición que conserve una escena de origen, consulta GPT Image 2.5 API o Nano Banana Pro API.
Si prefieres escribir un prompt antes que código, AI Image Generator ejecuta modelos de imagen en el navegador.
Preguntas frecuentes
¿Necesito una suscripción de Midjourney para usar esta API?
No. La clave viene de OmniaKey y las llamadas van a la API de terceros de OmniaKey. OmniaKey no es Midjourney; una suscripción del sitio web de Midjourney es un producto distinto y no crea una clave de OmniaKey.
¿Por qué una tarea de Midjourney devuelve cuatro imágenes en un solo archivo?
Midjourney devuelve una cuadrícula 2x2 con cuatro candidatos como un único PNG. Los cuatro comparten ese archivo, y en esta integración no hay upscale ni vary, así que divide la cuadrícula tú mismo tras la descarga si necesitas archivos separados.
¿Qué significa 409 idempotency_key_reused?
Enviaste una clave de idempotencia que OmniaKey ya tiene, pero el cuerpo de la solicitud cambió. Envía el cuerpo original o empieza un trabajo nuevo con otra clave. Reutilizar una clave con un prompt distinto es la causa habitual.
¿Qué pasa si mi script agota el tiempo mientras consulta?
El trabajo sigue en ejecución. El id de la tarea está en midjourney-task.json, así que al volver a ejecutar el mismo script consultará ese id o descargará el recurso terminado. Un tiempo de espera local deja vivo el trabajo y mantiene el cargo.
Modelos de este artículo
Artículos relacionados
Nano Banana Pro API: tutorial en Python, edición y precio
Un POST, un sondeo y una descarga: un script de Nano Banana Pro que funciona.
API de GPT Image 2.5: generar y editar imágenes con Python
Una petición POST crea la tarea y una GET lee el resultado. Aquí están el código de la petición, los tamaños que probamos el 29 de octubre de 2026 y una edición con su antes y después.
GPT Image 2.6: reseña, GIF, SVG, precio y para quién
Qué trae GPT Image 2.6, probado con nuestro propio GIF, tres carteles y una edición de foto.
Artículos recientes
- Claude Fable 5.2 vs Opus 5.5: código y tareas de agente
- Gemini 4 Hydrogen: código, niveles de pensamiento y API
- MiniMax M3.1 Flash en OpenCode: configuración y llamadas a herramientas
- Seedance 2.5 Flash: fecha de lanzamiento y acceso a la API
- Prompts de Seedream 5 Pro: texto en el póster y edición con referencias

