Design wechseln

ComfyUI API: Bilder per Workflow automatisiert in Serie erzeugen

Easton editorial illustration: large dark node-graph workspace with orange connected nodes

"Die offizielle ComfyUI-Server-Dokumentation beschreibt die Validierung und Queue-Aufnahme über /prompt sowie /ws, /history, /view, /queue und /interrupt."

Du hast einen stabilen ComfyUI-Workflow und brauchst für 200 Produkte jeweils vier Hauptbilder. Prompt und Seed für jeden Lauf in der Oberfläche zu ändern, skaliert nicht. Der erste POST an /prompt liefert jedoch node_errors, weil die Datei nicht im API-Format vorliegt. Nach erfolgreicher Ausführung zeigt /history nur filename und subfolder; die eigentliche Datei kommt über /view.

Der Weg von der GUI zur Skriptproduktion hat drei Hürden: das richtige API-Format, ein zuverlässiges Abschlussereignis per WebSocket und kontrollierte Parallelität ohne OOM. Die folgenden Abschnitte liefern Skriptgerüste, Parameter, Queue-Strategien und ein Backend-Checklist.

Lokale ComfyUI-Server-API starten

CLI-Start und Standardport

ComfyUI lauscht standardmäßig auf 127.0.0.1:8188. Für Prozesse auf demselben Rechner genügt der normale Start. Gib eine Listen-Adresse nur an, wenn Geräte im LAN zugreifen müssen:

# Nur lokaler Zugriff
python main.py --port 8188

# LAN-Zugriff; zusätzlich Firewall, Authentifizierung oder Reverse Proxy konfigurieren
python main.py --listen 0.0.0.0 --port 8188

Nach Starting server öffnest du http://127.0.0.1:8188. Erscheint die Oberfläche, ist der Dienst bereit.

Bei knappem VRAM kommen je nach Version --lowvram, --novram oder als langsame Notlösung --cpu infrage; bei viel Reserve kannst du --highvram testen. Ordne die Flags nicht starr einer Speicherkapazität zu: Modell, Präzision, VAE und Graph bestimmen die Spitze. Prüfe python main.py --help und die aktuelle offizielle Fehlerhilfe.

Wichtige API-Routen

Die lokalen Routen stehen in server.py. Für Skripte sind vor allem diese Endpunkte relevant:

RouteZweckParameter/Antwort
/promptWorkflow validieren und einreihenPOST {"prompt": workflow_dict, "client_id": "..."}; bei Erfolg prompt_id, bei Fehler node_errors
/history/{prompt_id}Verlauf und Ausgabemetadaten abrufenGET; outputs mit filename, subfolder und type
/view?filename=...&subfolder=...&type=...Ausgabedatei ladenGET; Binärdaten
/wsAusführungsstatus per WebSocketws://127.0.0.1:8188/ws?clientId=...
/queueQueue anzeigenGET; wartende Aufträge
/interruptAktuelle Ausführung stoppenPOST; für Timeouts
/upload/imageEingabebild hochladenPOST multipart/form-data
/object_infoNode-Typen und Parameter abfragenGET; prüft verfügbare Nodes

Routen können sich mit Versionen ändern. Vergleiche bei Abweichungen die aktuelle Dokumentation oder server.py.

WebSocket-Nachrichten

Beim Warten sind diese Nachrichtentypen wichtig:

  • status: Queue-Status einschließlich queue_remaining
  • execution_start: Start mit prompt_id
  • execution_cached: aus dem Cache übernommene Nodes
  • executing: aktueller Node; node is None und passende prompt_id bedeuten Abschluss
  • progress: aktueller und gesamter Schritt
  • executed: abgeschlossener Node mit Ausgabemetadaten

Die Abschlussprüfung sucht type == "executing", prüft data.node is None und vergleicht data.prompt_id mit der Antwort auf den Auftrag.

Workflow im API-Format exportieren

Schritte für Export Workflow (API)

Eine normale Workflow-JSON aus der Oberfläche ist nicht das API-Eingabeformat. Ein direkter POST kann deshalb mit node_errors scheitern.

  1. Lade einen Workflow, der in der ComfyUI-Oberfläche zuverlässig ein Bild erzeugt
  2. Wähle File -> Export Workflow (API); je nach Version heißt der Eintrag Save (API Format)
  3. Speichere eine .json wie workflow_api.json
  4. Prüfe numerische IDs wie "3" und "6" sowie class_type und inputs in jedem Node

Die Menübezeichnung kann sich ändern. Entscheidend ist die aktuelle Funktion zum API-Export.

API-Format gegenüber Save-Format

Das Save-Format enthält Layoutdaten; das API-Format nur ausführungsrelevante Informationen:

FormatInhaltVerwendung
Save formatNode-Positionen, Farben, Gruppen, Größen und visuelle LinksLayout in der GUI weiterbearbeiten
API formatnumerische Node-IDs, class_type, inputs, optional _metaper Skript oder API ausführen

Wenn Save-JSON an /prompt node_errors oder error liefert, lade sie in der GUI und exportiere sie erneut als API-Format.

Node-IDs verwalten

Für Parameter brauchst du die IDs der Prompt-, Seed-, Größen- und Ausgabeknoten:

  • Prompt node: CLIPTextEncode, in einfachen Beispielen oft "6"
  • Seed node: KSampler, in einfachen Beispielen oft "3"
  • Width/Height node: Eingaben von EmptyLatentImage oder einem Workflow-spezifischen Node
  • Output node: SaveImage oder SaveImageWebsocket

Notes und Groups können den Zweck dokumentieren. Das Skript muss dennoch die exportierte JSON prüfen. IDs gehören zum konkreten Graphen und können sich nach einem neuen Export ändern.

Minimales Skript: senden, warten, herunterladen

Der Ablauf besteht aus POST an /prompt, Warten auf den Abschluss und Abruf der Metadaten über /history mit anschließendem Download über /view.

HTTP-Submit-and-forget

Der kleinste Client reiht den Workflow ein und wartet nicht. Ein separater Worker kann den Status abfragen.

import json
import requests

# Workflow im API-Format laden
with open("workflow_api.json", "r") as f:
    workflow = json.load(f)

# Anfrage aufbauen
payload = {
    "prompt": workflow,
    "client_id": "my-script-client"  # Optional für die Zuordnung zu WebSocket-Ereignissen
}

# In die Queue stellen
response = requests.post("http://127.0.0.1:8188/prompt", json=payload)

if response.status_code == 200:
    result = response.json()
    prompt_id = result["prompt_id"]
    print(f"Gesendet: {prompt_id}")
else:
    error = response.json()
    print(f"Senden fehlgeschlagen: {error}")
    # node_errors enthält die Validierungsdetails

Erfolg liefert {"prompt_id": "...", "number": ...}, Fehler liefern {"error": {...}, "node_errors": {...}}. Der POST wartet nicht auf das Ergebnis.

Per WebSocket auf den Abschluss warten

WebSocket-Ereignisse vermeiden enges Polling. Verbinde dich, sende den Auftrag und warte auf das terminale executing-Ereignis.

import json
import uuid
import requests
import websocket

# Workflow laden
with open("workflow_api.json", "r") as f:
    workflow = json.load(f)

# client_id erzeugen
client_id = str(uuid.uuid4())

# WebSocket verbinden
ws = websocket.create_connection(f"ws://127.0.0.1:8188/ws?clientId={client_id}")

# Auftrag senden
payload = {"prompt": workflow, "client_id": client_id}
response = requests.post("http://127.0.0.1:8188/prompt", json=payload)
prompt_id = response.json()["prompt_id"]

# Auf Abschluss warten
while True:
    message = ws.recv()
    data = json.loads(message)

    if data["type"] == "executing":
        # None und dieselbe prompt_id bedeuten Abschluss
        if data["data"]["node"] is None and data["data"]["prompt_id"] == prompt_id:
            print("Auftrag abgeschlossen")
            break

ws.close()

Ergänze eine maximale Wartezeit, etwa 300 Sekunden. /interrupt stoppt die aktuell laufende Ausführung und muss entsprechend vorsichtig eingesetzt werden.

Bilder mit History und View laden

Nach dem Abschluss holt /history/{prompt_id} die Metadaten; /view lädt jede Datei.

import requests

# Auftragsverlauf abrufen
history_url = f"http://127.0.0.1:8188/history/{prompt_id}"
history = requests.get(history_url).json()

# Ausgabeknoten durchlaufen
outputs = history[prompt_id]["outputs"]
for node_id, node_output in outputs.items():
    if "images" in node_output:
        for image in node_output["images"]:
            filename = image["filename"]
            subfolder = image.get("subfolder", "")
            type = image.get("type", "output")

            # Download-URL bilden
            view_url = f"http://127.0.0.1:8188/view?filename={filename}&subfolder={subfolder}&type={type}"

            # Binärbild speichern
            img_data = requests.get(view_url).content
            with open(f"output_{filename}", "wb") as f:
                f.write(img_data)
            print(f"Gespeichert: output_{filename}")

outputs folgt {node_id: {"images": [{"filename": "...", "subfolder": "...", "type": "..."}]}}. History liefert Metadaten, /view die Binärdatei.

Stapel parametrisieren

Ein stabiler Workflow wird zur Vorlage. Pro Durchlauf änderst du nur ausgewählte Eingaben wie Prompt, Seed oder Größe.

Prompt, Seed und Abmessungen parametrisieren

Ändere Werte unter inputs:

import json
import random

# Workflow laden
with open("workflow_api.json", "r") as f:
    workflow = json.load(f)

# Prompt ändern; IDs an den eigenen Workflow anpassen
workflow["6"]["inputs"]["text"] = "a beautiful landscape, sunset, mountains"

# Zufälligen Seed erzeugen
workflow["3"]["inputs"]["seed"] = random.randint(0, 1000000)

# Abmessungen ändern
workflow["5"]["inputs"]["width"] = 1024
workflow["5"]["inputs"]["height"] = 768

# Auftrag senden...

Bestätige "6", "3" und "5" in deinem Export. Danach kann eine Schleife Prompt und Seed pro Auftrag ändern:

prompts = [
    "product photo, white background",
    "product photo, outdoor scene",
    "product photo, studio lighting"
]

for i, prompt_text in enumerate(prompts):
    workflow["6"]["inputs"]["text"] = prompt_text
    workflow["3"]["inputs"]["seed"] = random.randint(0, 1000000)

    # Auftrag senden
    payload = {"prompt": workflow, "client_id": client_id}
    response = requests.post("http://127.0.0.1:8188/prompt", json=payload)
    prompt_id = response.json()["prompt_id"]

    # Per WebSocket warten...
    # Ausgaben laden...

Auch die offiziellen Beispiele ändern KSampler.seed und CLIPTextEncode.text; sie sind gute Ausgangspunkte für die Node-Suche.

Queue-Strategien für Bildserien

Für 200 Bilder kannst du die Batch-Größe im Workflow erhöhen oder viele Prompts einreihen. Entscheidend sind Modell und gemessene VRAM-Reserve.

StrategieEigenschaftenGeeignet für
Ein Bild pro AuftragVRAM pro Job leichter kontrollierbarwenig Reserve, große Modelle, komplexe Workflows
Batch sizemehrere Bilder in einem Prompt, meist höhere VRAM-Spitzegemessene Reserve, kleinere Modelle, einfache Workflows
Parallele Anfragenmehrere Prompts mit begrenzter aktiver Zahlgemessene Kapazität und kontrollierte Worker

20 gleichzeitige Einreichungen können die Queue füllen und OOM oder einen Absturz auslösen.

Der konservative Start ist serielles Senden mit WebSocket-Abschluss und VRAM-Monitoring. Sende erst nach dem vorherigen Abschluss. Bei Speicherdruck reduzierst du die Last oder testest aktuelle Speicheroptionen.

Beispiel für Parallelitätskontrolle

Auf Multi-GPU-Systemen oder getrennten Cloud-Workern begrenzt ein Semaphore aktive Aufträge:

import copy
import threading

# Höchstens zwei aktive Aufträge
semaphore = threading.Semaphore(2)

def submit_and_wait(prompt_text, seed):
    with semaphore:
        # Pro Auftrag eine eigene Kopie statt gemeinsamem Zustand
        job_workflow = copy.deepcopy(workflow)
        job_workflow["6"]["inputs"]["text"] = prompt_text
        job_workflow["3"]["inputs"]["seed"] = seed

        # Senden und warten...
        # WebSocket-Schleife...

    # Freigabe für den nächsten Auftrag

# Stapel starten
threads = []
for i in range(50):
    t = threading.Thread(target=submit_and_wait, args=(prompts[i], seeds[i]))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Beginne mit Parallelität 1. Erhöhe sie erst nach Messungen von Speicherspitze, Latenz und Fehlern im echten Workflow. Präzision, VAE, Nachbearbeitung und Worker-Isolation verändern die Grenze.

VRAM überwachen

Frage Systemstatistiken ab und pausiere neue Jobs oberhalb eines Schwellenwerts:

import time

def check_vram(threshold=0.8):
    stats = requests.get("http://127.0.0.1:8188/system_stats").json()
    vram_used = stats["system_stats"]["devices"][0]["vram_used"]
    vram_total = stats["system_stats"]["devices"][0]["vram_total"]
    return (vram_used / vram_total) > threshold

# Vor jeder Einreichung prüfen
for prompt_text in prompts:
    while check_vram(0.85):
        print("Hohe VRAM-Auslastung; 30 Sekunden warten...")
        time.sleep(30)

    # Nächsten Auftrag senden...
    # Per WebSocket warten...

Die Spitze liegt häufig während KSampler; danach kann die Belegung fallen. Ein Intervall von 10–30 Sekunden reicht.

Stapelausgaben archivieren

Nutze vorhersehbare Namen wie {prompt_id}_{seed}_{timestamp}.png.

Speichere mindestens:

  • prompt_id: ComfyUI-Auftrags-ID
  • seed: Zufallswert
  • prompt_text: verwendeter Prompt
  • width/height: Ausgabegröße
  • timestamp: Erzeugungszeit
  • business_id: fachliche ID wie SKU oder Bestellnummer

Ordne nach Datum oder Stapel, etwa outputs/20260624/batch_001/. SQLite oder PostgreSQL können Metadaten mit Dateipfaden verknüpfen.

Backend-Checkliste

Ein Backend braucht Request-IDs, Timeouts, Queue-Limits, Speicherüberwachung und explizite Fehlerbehandlung. Ein funktionierendes Skript ist noch kein stabiler Dienst.

Request-ID und Idempotenz

Erzeuge eine eindeutige request_id und ordne sie der ComfyUI-prompt_id zu. Für abgeschlossene IDs lieferst du das bestehende Ergebnis.

import uuid

# Fachliche Request-ID
request_id = str(uuid.uuid4())

# Zuordnung in Datenbank oder Cache speichern
request_prompt_map[request_id] = prompt_id

# Bei Duplikaten bestehendes Ergebnis zurückgeben
if request_id in completed_requests:
    return get_cached_result(request_id)

ComfyUI erzeugt die prompt_id; sie ist nicht die fachliche request_id. Speichere die Zuordnung dauerhaft.

Timeout und Abbruch

Setze eine maximale Laufzeit, etwa 300 Sekunden, und stoppe die aktuelle Ausführung bei Überschreitung:

import time

timeout = 300  # Fünf Minuten
start_time = time.time()

# Auf WebSocket-Nachrichten warten...
while True:
    elapsed = time.time() - start_time
    if elapsed > timeout:
        # Aktuelle Ausführung stoppen
        requests.post("http://127.0.0.1:8188/interrupt")
        print("Auftrag wegen Timeout unterbrochen")
        break

    # Normale Nachrichten verarbeiten...

Bei WebSocket-Abbruch oder längerer Stille verbindest du neu oder nutzt History. Prüfe danach /queue und entscheide über wartende Jobs.

Queue-Limit und VRAM

Lege etwa fünf wartende Jobs als Obergrenze fest und lehne weitere Anfragen ab oder verschiebe sie. Begrenze auch aktive Worker.

stats = requests.get("http://127.0.0.1:8188/system_stats").json()
vram_used = stats["system_stats"]["devices"][0]["vram_used"]
vram_total = stats["system_stats"]["devices"][0]["vram_total"]
vram_percent = vram_used / vram_total

if vram_percent > 0.8:
    print("Hohe VRAM-Auslastung; neue Anfrage abgelehnt")

Bei Druck wartest du auf eine leere Queue oder weist neue Arbeit zurück. Reduziere Batch-Größe und Workflow-Komplexität vor identischen Wiederholungen.

Fehlerklassen und Wiederholungen

Jeder Fehler braucht eine eigene Behandlung:

FehlerUrsacheBehandlung
node_errorsModell/Node fehlt, Parameter ungültig, Eingabe fehltWorkflow und Umgebung korrigieren; nicht wiederholen
OOMzu wenig VRAMBatch oder Last reduzieren und geprüfte Speicheroption ändern; nicht unverändert wiederholen
WebSocket-AbbruchNetzwerkproblemneu verbinden und /history/{prompt_id} abfragen
Timeoutlangsames Laden oder komplexer WorkflowTimeout erhöhen oder vereinfachen; höchstens einmal wiederholen
DienstabsturzSpeicher erschöpft oder GPU-FehlerLogs prüfen, Dienst neu starten, vorsichtig wiederholen

node_errors nennt fehlende Node-Klassen und unpassende Eingabetypen. Repariere Workflow, custom nodes oder Modelldateien. Auch OOM braucht eine Konfigurationsänderung.

Cloud API und lokale API vergleichen

Comfy Cloud führt Workflows gehostet aus, unterscheidet sich aber bei Authentifizierung, Jobstatus, WebSocket-Adresse und Parallelität. Das API-Format bleibt verwendbar; Endpunkte müssen der aktuellen Cloud API Reference folgen.

Unterschiede der Routen

FunktionLokale APICloud API
Auftrag senden/prompt/api/prompt
Status/Ergebnis/history/{prompt_id}/api/job/{prompt_id}/status und /api/jobs/{job_id}
Ausgabe laden/view/api/view
WebSocket/ws?clientId=.../ws?clientId=...&token=...

Cloud benötigt X-API-Key und ein aktives Abo. Die lokale API lauscht standardmäßig nur auf 127.0.0.1; bei --listen, Proxy oder Portweiterleitung musst du Authentifizierung und Zugriffsschutz ergänzen. Cloud bleibt experimentell. Das alte /api/history_v2/{prompt_id} ist zugunsten von /api/jobs/{job_id} veraltet.

Parallelität und Abo-Grenzen

Die Cloud-Parallelität hängt vom Tarif ab; zusätzliche Jobs warten. Ausgaben liegen im Cloud-Speicher, und /api/view liefert eine temporäre signierte URL.

Tarife, Limits, Laufzeit und Preise ändern sich. Cloud erspart lokale GPU-Verwaltung; lokal verantwortest du Queue, VRAM, Sicherheit und Stabilität.

Nächste Schritte

Innerhalb der Serie

Diese Seite führt vom funktionierenden Bild-Workflow zur programmierbaren Stapelproduktion:

  • ComfyUI-Workflows wiederverwenden: Import, fehlende Nodes und Modellpfade
  • ComfyUI mit wenig VRAM beschleunigen: Speicheroptionen, Batch, OOM und Tiled VAE
  • Videoerzeugung mit ComfyUI: Grenze zwischen Bildstapeln und Video-Workflows
  • ComfyUI warten: fehlende Nodes, Startfehler und Versionskonflikte

Verwandte Themen

Für allgemeinere Automatisierung:

  • KI-Workflows mit n8n: ComfyUI mit mehreren Werkzeugen verbinden
  • Ollama API: programmatische Aufrufe, Queues und strukturierte Ausgabe
  • Strukturierte LLM-Ausgabe: zuverlässige Daten und API-Integration

Fazit

Der Weg zur Skriptproduktion lautet: API-Workflow exportieren, per WebSocket warten, Dateien über /history und /view laden, ausgewählte Eingaben parametrisieren und danach Request-ID, Timeout, Queue-Limit und VRAM-Monitoring ergänzen.

Starte mit einem Workflow und einem Einzelbild. Erzeuge danach zehn parametrisierte Bilder und beobachte Speicher sowie Queue. Erst wenn dieser kleine Stapel stabil ist, ergänzt du Idempotenz, Abbruch, Monitoring und Ergebnisprotokolle.

Den ersten Bildstapel über die ComfyUI API ausführen

Prüfe den lokalen Automatisierungsweg vom Workflow-Export bis zur Ergebnisablage.

  1. 1

    Step 1: GUI-Workflow prüfen

    Erzeuge in ComfyUI zuverlässig ein Bild und prüfe Modelle, custom nodes, Eingabedateien und Ausgabeknoten.
  2. 2

    Step 2: API-Format exportieren

    Nutze Export Workflow (API) in der aktuellen Oberfläche und prüfe, ob jeder Knoten class_type und inputs enthält.
  3. 3

    Step 3: Einen Auftrag senden

    Sende den Workflow im Feld prompt per POST an /prompt, speichere die prompt_id und lies bei Fehlern node_errors.
  4. 4

    Step 4: Warten und herunterladen

    Warte über /ws auf das Abschlussereignis oder frage /history/{prompt_id} ab. Lade danach jede Ausgabe über /view.
  5. 5

    Step 5: Wenige Eingaben parametrisieren

    Kopiere die Vorlage, ändere prompt, seed, width, height und filename_prefix einzeln und prüfe Node-ID sowie class_type.
  6. 6

    Step 6: Schutz für Stapel ergänzen

    Starte seriell und ergänze eigene job_id, Idempotenz, Queue-Limit, Timeout, VRAM-Monitoring, Fehlerklassen und Ergebnisablage.

FAQ

Welche Workflow-JSON benötigt die ComfyUI API?
Verwende einen Workflow im API-Format. Das normale Save-Format enthält Layoutdaten für die GUI. Lade den Workflow in ComfyUI und exportiere ihn mit Export Workflow (API).
Wie sieht der kleinste lokale ComfyUI-API-Ablauf aus?
Sende den Workflow an /prompt, speichere die prompt_id, warte per WebSocket oder /history/{prompt_id} und rufe /view mit filename, subfolder und type aus der History auf.
Was bedeutet node_errors?
Die Antwort enthält Validierungsfehler pro Knoten, etwa fehlende Nodes oder Modelle, falsche Typen und fehlende Eingaben. Behebe Workflow oder Laufzeitumgebung, statt unverändert erneut zu senden.
Kann ich Ergebnisse nach einem WebSocket-Abbruch noch abrufen?
Ja. Bewahre die prompt_id auf, verbinde dich erneut oder frage /history/{prompt_id} und /queue ab. WebSocket liefert Live-Status, ist aber nicht das einzige Auftragsprotokoll.
Soll ich die Batch-Größe erhöhen oder viele Prompts senden?
Auf einer lokalen Einzel-GPU ist Batch 1 mit seriellen Aufträgen der sichere Start. Eine größere Batch-Größe erhöht meist die VRAM-Spitze; eine Queue erleichtert Fehlertrennung, Wiederholungen und Ablage.
Darf ich mehrere Prompts parallel senden?
Ja, aber beginne mit Parallelität 1, nutze pro Auftrag eine eigene Workflow-Kopie und erhöhe erst nach Messungen von Modell, VRAM-Spitze, Queue-Länge und Fehlerrate.
Sind Comfy Cloud API und lokale API identisch?
Nein. Beide verwenden API-Workflows, doch Cloud benötigt X-API-Key und Abo. Status-, WebSocket- und Ergebnisendpunkte unterscheiden sich; die offizielle Dokumentation kennzeichnet die Cloud API weiterhin als experimentell.

10 Min. Lesezeit · Veröffentlicht am: 24. Juli 2026 · Aktualisiert am: 24. Juli 2026

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog