GPT-Image 2.6 Flare und Sunburst sind liveAb $0.025 pro Bild

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.

Kiki, OmniaKey COO8 Min. Lesezeit
Auf dieser Seite

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/tasks mit einem Idempotency-Key, frage dann GET /v1/media/tasks/{id} ab und lade assets[0].url herunter.
  • 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:

  1. Einen OmniaKey-API-Schlüssel, exportiert als OMNIAKEY_API_KEY.
  2. Python 3. Das Skript nutzt nur die Standardbibliothek, du musst also kein SDK installieren.
  3. midjourney fü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.
bash
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.

FeldWertHinweis
input.promptTextDie gewünschte Szene
parameters.aspectRatio1:1Auch 16:9, 9:16, 3:2, 2:3
parameters.quality"1"Die Docs nutzen die Zeichenkette "1"
parameters.n1Muss 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:

json
{
  "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.

python
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:

Vier gefaltete rote Papierboote auf blauem Wasser bei Sonnenaufgang, in einem 2x2-Raster mit warmem reflektiertem Licht
Probe aus dem Integrationstest vom 12. November 2026: eine midjourney-Aufgabe, ein PNG mit 2048x2048 Pixeln, vier Varianten, 197,1 Sekunden von Create bis Download. Die heruntergeladenen Bytes stimmten mit dem SHA-256 der Aufgabe überein.

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:

json
{
  "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.

AntwortCodeWas tun
400missing_idempotency_keyHeader ergänzen; unter 255 Zeichen halten
409idempotency_key_reusedUrsprünglichen Body senden oder mit neuem Schlüssel einen neuen Job starten
503no_available_channelZugriff für diesen Schlüssel/dieses Konto prüfen; bei Fortbestehen den Support kontaktieren
failederror.code, error.messageAufgabenfehler 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

Midjourney APIPythonBild-API2x2-RasterAPI-Tutorial

Verwandte Artikel

Neueste Beiträge

Beliebte Anleitungen