Midjourney API mit Python: Bilder erzeugen und speichern
Aufgabe anlegen, bis zum Abschluss abfragen, dann ein 2x2-PNG-Raster speichern; Schlüssel und Body liegen vor dem POST auf der Platte.
Auf dieser Seite
- Kurz gesagt
- Welche API nutzt dieses Python-Beispiel?
- Was du brauchst
- Request-Body und Parameter
- Das Python-Skript
- Was zurückkommt
- Nach Timeout oder fehlgeschlagenem Download erneut ausführen
- Fehler und Lösungen
- Kosten und wann eine andere Bild-API sinnvoll ist
- Häufige Fragen
- Brauche ich ein Midjourney-Abo, um diese API zu nutzen?
- Warum liefert eine Midjourney-Aufgabe vier Bilder in einer Datei?
- Was bedeutet 409 idempotency_key_reused?
- Was passiert, wenn mein Skript beim Abfragen in einen Timeout läuft?
Ein Midjourney-Job auf OmniaKey braucht in Python drei Schritte: Aufgabe anlegen, Status bis zum Abschluss abfragen und das PNG von der signierten Asset-URL herunterladen. Ein Integrationstest am 12. November 2026 lieferte ein Bild mit 2048x2048 Pixeln in 197,1 Sekunden, und dieses Bild ist ein 2x2-Raster mit vier Varianten. Das Skript unten ergänzt eine Statusdatei, damit ein Timeout oder ein fehlgeschlagener Download keinen zweiten bezahlten Job erzeugt.
Kurz gesagt
- Sende
POST https://api.omniakey.com/v1/media/tasksmit einemIdempotency-Key, frage dannGET /v1/media/tasks/{id}ab und ladeassets[0].urlherunter. - Eine Aufgabe liefert ein PNG-Raster mit vier Bildern, keine vier getrennten Dateien.
- Midjourney macht in dieser API nur Text zu Bild: kein Editieren, Referenzbild, Blend, Upscale, Vary, Verschieben und keine Versionsauswahl.
- Der Schlüssel kommt von OmniaKey und ist von einem Midjourney-Abo getrennt. Speichere Body und Idempotenzschlüssel vor dem POST, ergänze die Task-ID, sobald die API die Anfrage annimmt, und behalte die Statusdatei, um später fortzufahren. Ein lokaler Timeout bricht die Aufgabe auf dem Server nicht ab.
Welche API nutzt dieses Python-Beispiel?
Das Beispiel ruft OmniaKeys Drittanbieter-API für Midjourney auf. OmniaKey ist nicht Midjourney, und ein OmniaKey-Schlüssel gehört zu einem eigenen Konto mit eigenem Guthaben. Ein Abo auf der Midjourney-Website ist ein separates Produkt und erzeugt keinen OmniaKey-Schlüssel.
Die Midjourney-Modellseite zeigt das Modell und den aktuellen Preis auf OmniaKeys Seite. Der Rest der Anleitung behandelt diese Integration und den Ablauf ihrer Aufgaben.
Was du brauchst
Drei Dinge:
- Einen OmniaKey-API-Schlüssel, exportiert als
OMNIAKEY_API_KEY. - Python 3. Das Skript nutzt nur die Standardbibliothek, du musst also kein SDK installieren.
midjourneyfür diesen Schlüssel/dieses Konto. Fehlt das Modell in der Modellliste des Schlüssels, prüfe den Kontozugriff, bevor du den Create-Aufruf startest.
export OMNIAKEY_API_KEY="your-key"
python3 midjourney_grid.py
Halte den Schlüssel aus dem Skript und aus Logs heraus. Der Code liest ihn einmal aus der Umgebung.
Request-Body und Parameter
Midjourney bekommt einen Prompt und drei Parameter. Der Bot-Typ ist auf OmniaKeys Seite voreingestellt, deshalb kann der Body Modellversion und botType weglassen.
| Feld | Wert | Hinweis |
|---|---|---|
input.prompt | Text | Die gewünschte Szene |
parameters.aspectRatio | 1:1 | Auch 16:9, 9:16, 3:2, 2:3 |
parameters.quality | "1" | Die Docs nutzen die Zeichenkette "1" |
parameters.n | 1 | Muss 1 sein |
Lege die Form mit aspectRatio fest, nicht mit einer Pixelgröße. Das Feld ist camelCase: aspectRatio, nie aspect_ratio. Nutze einen der fünf genannten Werte.
Der Body für den Beispiel-Prompt sieht so aus:
{
"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}
}
Das Python-Skript
Speichere es als midjourney_grid.py und führe es aus. Der erste Lauf schreibt midjourney-task.json vor dem POST und ergänzt die Task-ID, sobald OmniaKey die Anfrage annimmt.
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()
Die Formen von Request und Response stammen aus dem Integrationstest vom 12. November; der Neustart-Status ist hier die Ergänzung, und seine Ablaufpfade wurden offline mit simulierten Antworten geprüft.
Die Statusdatei bewahrt, was du zum Fortsetzen brauchst: den JSON-Body und die UUID als Idempotenzschlüssel. Einen API-Schlüssel speichert sie nicht. Sie wird vor dem POST geschrieben, sodass eine abgebrochene Verbindung beide Werte zur Wiederherstellung auf der Platte lässt.
Der Create-Aufruf kann zwei Erfolgscodes liefern. 202 nimmt einen neuen Job an. 200 bedeutet, dass OmniaKey denselben Schlüssel und denselben Body schon gesehen hat und die ursprüngliche Aufgabe zurückgibt; frage diese Aufgabe weiter ab. Sende denselben Body und Schlüssel nur erneut, wenn das Ergebnis der Erstellung nach einem Verbindungsfehler unklar ist. Die Docs nennen 409 idempotency_key_reused für einen geänderten Body.
Steuere die Abfrageschleife über status. Die Prozentangabe ist ein Richtwert und kann bei 0 bleiben, während der Job läuft. Stoppe bei succeeded, failed oder canceled. OmniaKeys Dokumentation zum Ablauf von Media-Aufgaben (geprüft am 12. November 2026) beschreibt diese Zustände.
Das Abfragelimit ist lokal. Läuft es ab, ist der Job weiter aktiv oder in der Warteschlange; ein erneuter Lauf desselben Skripts liest die gespeicherte Task-ID und fragt weiter ab. Ein lokaler Timeout ist kein Abbruch. Für den Abbruch gibt es einen eigenen Endpoint.
Was zurückkommt
Eine erfolgreiche Aufgabe hat ein Asset: ein PNG-Raster. Das ist die Probe aus dem Integrationstest vom 12. November 2026:

Die Antwort unten ist gekürzt: Die URL ist unkenntlich gemacht und expires_at ist historisch. Sie ergänzt die Asset-Felder, die du brauchst:
{
"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…"
}
]
}
Lies width und height aus dem Asset, statt 2048 fest zu verdrahten. Das Quadrat mit 2048x2048 und die 197,1 Sekunden sind eine Probe; behandle sie so. Alle vier Varianten teilen sich ein PNG, teile das Raster nach dem Download, wenn deine App einzelne Bilder braucht.
Die url ist signiert und kurzlebig, etwa eine Stunde. Aufgaben-Datensatz und Bild werden laut OmniaKeys Bild-API-Docs (geprüft am 12. November 2026) 24 Stunden nach dem Anlegen aufbewahrt. Lade vor Ablauf der URL herunter. Ist sie abgelaufen, frage die Task-ID erneut ab, um eine neue signierte URL zu bekommen.
Lade die Asset-URL mit einem normalen GET und ohne Authorization-Header herunter. Die Signatur steckt in der URL. Das Skript vergleicht außerdem die heruntergeladenen Bytes mit dem sha256 der Aufgabe; so fällt eine abgeschnittene Datei auf, bevor sie deine gute Kopie überschreibt.
Nach Timeout oder fehlgeschlagenem Download erneut ausführen
Führe denselben Befehl erneut aus und behalte den Job. Das Skript liest midjourney-task.json und nimmt den kürzesten sicheren Weg:
- Create unbestätigt: Es sendet denselben Body mit demselben Idempotenzschlüssel per POST.
- Task-ID vorhanden und Job läuft noch: Es fragt die vorhandene ID ab.
- Aufgabe erfolgreich, aber PNG fehlt: Es fragt einmal nach einer neuen signierten URL und lädt erneut herunter.
- PNG ist gespeichert und stimmt mit dem gespeicherten SHA-256: Es beendet sich ohne weitere Anfrage. Eine beschädigte Datei wird erneut abgefragt und heruntergeladen.
Das funktioniert innerhalb der 24-Stunden-Aufbewahrung. Derselbe Schlüssel mit geändertem Prompt oder geänderten Parametern ergibt 409. Das Skript lehnt eine Statusdatei ab, deren Prompt nicht mehr passt; verschiebe die Datei, wenn du Parameter änderst.
Fehler und Lösungen
Die Fehler unten folgen aus den Aufgaben- und Idempotenzregeln der API. Behebe die Ursache und starte dann gegebenenfalls mit derselben Statusdatei neu.
| Antwort | Code | Was tun |
|---|---|---|
| 400 | missing_idempotency_key | Header ergänzen; unter 255 Zeichen halten |
| 409 | idempotency_key_reused | Ursprünglichen Body senden oder mit neuem Schlüssel einen neuen Job starten |
| 503 | no_available_channel | Zugriff für diesen Schlüssel/dieses Konto prüfen; bei Fortbestehen den Support kontaktieren |
failed | error.code, error.message | Aufgabenfehler lesen; fehlgeschlagene Jobs werden erstattet |
canceled | — | Der Job wurde gestoppt; lege einen neuen an, wenn du ihn noch brauchst |
503 no_available_channel belegt nicht, dass einem Schlüssel die Modellberechtigung fehlt. Zwei Einsendungen mit einem in diesem Batch verwendeten Schlüssel lieferten 503 und erzeugten keine Aufgabe; ein anderer Schlüssel war in einem früheren Integrationstest am selben Tag erfolgreich. Die Ursache ist unbekannt: Es kann die Verfügbarkeit des Schlüssels/Kontos oder des Upstream-Kanals sein. Prüfe, ob midjourney für deinen aktuellen Schlüssel/dein Konto verfügbar ist. Bleibt der 503, schicke Support den Response-Body und die Request-ID.
Ein 200 beim Create ist die Wiederholung der ursprünglichen Aufgabe. Nur failed und canceled sind endgültige Fehler; stoppst du das Skript, läuft der Job weiter.
Kosten und wann eine andere Bild-API sinnvoll ist
OmniaKey berechnet einmal pro erfolgreicher Aufgabe, fehlgeschlagene oder abgebrochene Aufgaben werden automatisch erstattet. Der aktuelle Preis steht auf der Midjourney-Modellseite; prüfe ihn, bevor du einen Batch schätzt.
Midjourney passt, wenn du vier Varianten in einem Raster vergleichen willst. Diese Integration liefert dieses Text-zu-Bild-Raster; für ein Bild pro Aufruf oder eine Bearbeitung, die eine Ausgangsszene behält, sieh dir GPT Image 2.5 API oder Nano Banana Pro API an.
Wenn du lieber einen Prompt tippst als Code schreibst, führt AI Image Generator Bildmodelle im Browser aus.
Häufige Fragen
Brauche ich ein Midjourney-Abo, um diese API zu nutzen?
Nein. Der Schlüssel kommt von OmniaKey und die Aufrufe gehen an OmniaKeys Drittanbieter-API. OmniaKey ist nicht Midjourney; ein Abo auf der Midjourney-Website ist ein separates Produkt und erzeugt keinen OmniaKey-Schlüssel.
Warum liefert eine Midjourney-Aufgabe vier Bilder in einer Datei?
Midjourney gibt ein 2x2-Raster mit vier Varianten als ein PNG zurück. Alle vier teilen diese Datei, und Upscale und Vary sind in dieser Integration nicht verfügbar; teile das Raster nach dem Download selbst, wenn du einzelne Dateien brauchst.
Was bedeutet 409 idempotency_key_reused?
Du hast einen Idempotenzschlüssel gesendet, den OmniaKey schon kennt, aber der Request-Body hat sich geändert. Sende den ursprünglichen Body oder starte mit einem neuen Schlüssel einen neuen Job. Denselben Schlüssel mit einem anderen Prompt zu verwenden, ist die übliche Ursache.
Was passiert, wenn mein Skript beim Abfragen in einen Timeout läuft?
Der Job läuft weiter. Die Task-ID steht in midjourney-task.json; ein erneuter Lauf desselben Skripts fragt diese ID ab oder lädt das fertige Asset herunter. Ein lokaler Timeout lässt Job und Berechnung bestehen.
Modelle in diesem Artikel
Verwandte Artikel
Nano Banana Pro API: Python-Tutorial, Bildbearbeitung und Preis
Ein POST, ein Poll, ein Download: ein Nano-Banana-Pro-Skript, das läuft.
GPT Image 2.5 API: Bilder mit Python generieren und bearbeiten
Ein POST legt den Task an, ein GET liest das Ergebnis. Hier kommen der Request-Code, die am 29. Oktober 2026 getesteten Größen und eine Bearbeitung mit Vorher und Nachher.
GPT Image 2.6 Test: GIF, SVG, Preis und für wen
Was GPT Image 2.6 kann, geprüft mit unserem eigenen GIF, drei Postern und einem Foto-Edit.

