ComfyUI API: Bilder per Workflow automatisiert in Serie erzeugen

"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:
| Route | Zweck | Parameter/Antwort |
|---|---|---|
/prompt | Workflow validieren und einreihen | POST {"prompt": workflow_dict, "client_id": "..."}; bei Erfolg prompt_id, bei Fehler node_errors |
/history/{prompt_id} | Verlauf und Ausgabemetadaten abrufen | GET; outputs mit filename, subfolder und type |
/view?filename=...&subfolder=...&type=... | Ausgabedatei laden | GET; Binärdaten |
/ws | Ausführungsstatus per WebSocket | ws://127.0.0.1:8188/ws?clientId=... |
/queue | Queue anzeigen | GET; wartende Aufträge |
/interrupt | Aktuelle Ausführung stoppen | POST; für Timeouts |
/upload/image | Eingabebild hochladen | POST multipart/form-data |
/object_info | Node-Typen und Parameter abfragen | GET; 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ßlichqueue_remainingexecution_start: Start mitprompt_idexecution_cached: aus dem Cache übernommene Nodesexecuting: aktueller Node;node is Noneund passendeprompt_idbedeuten Abschlussprogress: aktueller und gesamter Schrittexecuted: 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.
- Lade einen Workflow, der in der ComfyUI-Oberfläche zuverlässig ein Bild erzeugt
- Wähle
File -> Export Workflow (API); je nach Version heißt der EintragSave (API Format) - Speichere eine
.jsonwieworkflow_api.json - Prüfe numerische IDs wie
"3"und"6"sowieclass_typeundinputsin 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:
| Format | Inhalt | Verwendung |
|---|---|---|
| Save format | Node-Positionen, Farben, Gruppen, Größen und visuelle Links | Layout in der GUI weiterbearbeiten |
| API format | numerische Node-IDs, class_type, inputs, optional _meta | per 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
EmptyLatentImageoder einem Workflow-spezifischen Node - Output node:
SaveImageoderSaveImageWebsocket
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.
| Strategie | Eigenschaften | Geeignet für |
|---|---|---|
| Ein Bild pro Auftrag | VRAM pro Job leichter kontrollierbar | wenig Reserve, große Modelle, komplexe Workflows |
| Batch size | mehrere Bilder in einem Prompt, meist höhere VRAM-Spitze | gemessene Reserve, kleinere Modelle, einfache Workflows |
| Parallele Anfragen | mehrere Prompts mit begrenzter aktiver Zahl | gemessene 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-IDseed: Zufallswertprompt_text: verwendeter Promptwidth/height: Ausgabegrößetimestamp: Erzeugungszeitbusiness_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:
| Fehler | Ursache | Behandlung |
|---|---|---|
node_errors | Modell/Node fehlt, Parameter ungültig, Eingabe fehlt | Workflow und Umgebung korrigieren; nicht wiederholen |
| OOM | zu wenig VRAM | Batch oder Last reduzieren und geprüfte Speicheroption ändern; nicht unverändert wiederholen |
| WebSocket-Abbruch | Netzwerkproblem | neu verbinden und /history/{prompt_id} abfragen |
| Timeout | langsames Laden oder komplexer Workflow | Timeout erhöhen oder vereinfachen; höchstens einmal wiederholen |
| Dienstabsturz | Speicher erschöpft oder GPU-Fehler | Logs 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
| Funktion | Lokale API | Cloud 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
Step 1: GUI-Workflow prüfen
Erzeuge in ComfyUI zuverlässig ein Bild und prüfe Modelle, custom nodes, Eingabedateien und Ausgabeknoten. - 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
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
Step 4: Warten und herunterladen
Warte über /ws auf das Abschlussereignis oder frage /history/{prompt_id} ab. Lade danach jede Ausgabe über /view. - 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
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?
Wie sieht der kleinste lokale ComfyUI-API-Ablauf aus?
Was bedeutet node_errors?
Kann ich Ergebnisse nach einem WebSocket-Abbruch noch abrufen?
Soll ich die Batch-Größe erhöhen oder viele Prompts senden?
Darf ich mehrere Prompts parallel senden?
Sind Comfy Cloud API und lokale API identisch?
10 Min. Lesezeit · Veröffentlicht am: 24. Juli 2026 · Aktualisiert am: 24. Juli 2026
ComfyUI & Stable Diffusion Praxisleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.



Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen