Tutorial · Einsteiger
ComfyUI API Grundlagen: Workflows programmatisch steuern
ComfyUI ist nicht nur eine grafische Oberfläche – jeder Workflow lässt sich auch per API auslösen. Damit automatisierst du die Bildgenerierung, bindest sie in Apps ein oder erzeugst ganze Serien. Dieses Tutorial zeigt den kompletten Weg vom Server-Start bis zum abgeholten Bild.
Voraussetzungen & Use-Cases
Du solltest ComfyUI bereits über die GUI bedienen können und einen einfachen Text-zu-Bild-Workflow
kennen. Außerdem brauchst du Python 3.9+ und die Bibliothek requests
(pip install requests).
Typische Anwendungsfälle für die API:
- Batch-Processing – hunderte Varianten automatisch erzeugen.
- Integration – Bildgenerierung aus einer eigenen App oder einem Backend heraus.
- Pipelines/CI – Assets reproduzierbar per Skript generieren.
Die API ist eine gewöhnliche HTTP-Schnittstelle. Wenn dir Begriffe wie POST oder
Status-Codes noch nichts sagen, lies zuerst die REST-API-Grundlagen.
API-Server starten
Die ComfyUI-API läuft im selben Prozess wie die GUI – du musst nichts Zusätzliches installieren. Starte ComfyUI wie gewohnt:
# Startet Server + API unter http://127.0.0.1:8188
python main.py
# Prüfen, ob der Server antwortet:
curl http://127.0.0.1:8188/system_stats
Mit --listen 0.0.0.0 ist der Server im gesamten Netzwerk erreichbar – ComfyUI hat
keine eingebaute Authentifizierung. Nutze das nur in vertrauenswürdigen Netzen oder hinter
einem Reverse-Proxy mit Zugriffsschutz.
Workflow im API-Format exportieren
Wichtig: Die API erwartet nicht das normale Workflow-JSON aus dem Browser, sondern das schlankere API-Format (ohne Layout-Infos wie Positionen oder Farben). So kommst du dran:
- Menü:
Workflow → Export (API)bzw. in älteren VersionenFile → Export Workflow (API)lädt eine.json-Datei mit nur den API-Daten herunter. - Falls der Menüpunkt fehlt: in den Einstellungen (Zahnrad) die Dev Mode Options aktivieren – dann erscheint der Button „Save (API Format)“.
Das exportierte JSON ist eine Sammlung von Nodes. Jeder Node hat eine ID, einen
class_type und seine inputs. Verweise auf andere Nodes stehen als Liste
["node_id", output_index]:
{
"3": {
"class_type": "KSampler",
"inputs": {
"seed": 42,
"steps": 20,
"cfg": 8,
"sampler_name": "euler",
"model": ["4", 0],
"positive": ["6", 0],
"latent_image": ["5", 0]
}
},
"6": {
"class_type": "CLIPTextEncode",
"inputs": { "text": "a red sports car", "clip": ["4", 1] }
}
}
Um den Prompt später per Skript zu ändern, musst du wissen, welcher Node dein Text-Prompt ist
(hier Node "6", ein CLIPTextEncode). Die IDs siehst du im exportierten JSON.
Ersten API-Call senden
Ein Workflow wird per POST /prompt in die Warteschlange gelegt. Der Body muss die Form
{ "prompt": <workflow> } haben. Als Antwort kommt eine prompt_id zurück.
import requests
import json
import time
SERVER = "http://127.0.0.1:8188"
# 1. Workflow im API-Format laden
with open("workflow_api.json", "r", encoding="utf-8") as f:
workflow = json.load(f)
# 2. Prompt-Text dynamisch setzen (Node "6" = CLIPTextEncode)
workflow["6"]["inputs"]["text"] = "a red sports car, studio lighting"
# 3. Workflow in die Queue legen
resp = requests.post(f"{SERVER}/prompt", json={"prompt": workflow})
resp.raise_for_status()
prompt_id = resp.json()["prompt_id"]
print("Prompt-ID:", prompt_id)
Als curl-Variante muss der Body ebenfalls in {"prompt": …} verpackt sein. Die exportierte
Datei enthält aber nur den Workflow selbst – mit jq wickelst du sie passend ein:
jq '{prompt: .}' workflow_api.json \
| curl -X POST http://127.0.0.1:8188/prompt \
-H "Content-Type: application/json" \
-d @-
Bilder abrufen
Die Generierung läuft asynchron. Du fragst GET /history/{prompt_id} ab, bis dein Auftrag
dort auftaucht. Die fertigen Bilder holst du dann über GET /view – dieser Endpunkt braucht
drei Parameter: filename, subfolder und type
(meist output).
# 4. Auf Fertigstellung warten (Polling der History)
while True:
history = requests.get(f"{SERVER}/history/{prompt_id}").json()
if prompt_id in history:
outputs = history[prompt_id]["outputs"]
break
time.sleep(1)
# 5. Bild(er) herunterladen
for node_id, node_output in outputs.items():
for image in node_output.get("images", []):
params = {
"filename": image["filename"],
"subfolder": image["subfolder"], # oft leer ""
"type": image["type"], # meist "output"
}
data = requests.get(f"{SERVER}/view", params=params).content
with open(image["filename"], "wb") as out:
out.write(data)
print("Gespeichert:", image["filename"])
Die History-Antwort ist dabei so aufgebaut (vereinfacht):
{
"<prompt_id>": {
"outputs": {
"9": {
"images": [
{ "filename": "ComfyUI_00001_.png", "subfolder": "", "type": "output" }
]
}
}
}
}
Robuster: WebSocket statt Polling
Polling funktioniert, ist aber ineffizient. Sauberer ist der WebSocket-Endpunkt
ws://127.0.0.1:8188/ws?clientId=<uuid>: Er meldet in Echtzeit Fortschritt und
Fertigstellung. Übergib dieselbe clientId zusätzlich im /prompt-Body, damit du
die Events zuordnen kannst. Der Auftrag ist fertig, wenn eine executing-Nachricht mit
node: null und passender prompt_id eintrifft. Für den Einstieg reicht das
Polling oben – für produktive Setups lohnt sich der Umstieg auf WebSocket.
Troubleshooting
| Problem | Ursache & Lösung |
|---|---|
| Port 8188 belegt | Anderen Port wählen: python main.py --port 8189 |
| Browser-Zugriff blockiert (CORS) | Server mit --enable-cors-header starten |
KeyError: 'prompt_id' | node_errors in der Antwort prüfen – meist ist das Workflow-JSON ungültig oder ein Model fehlt |
| Bild wird nicht gefunden (404 bei /view) | subfolder und type aus der History mitgeben, nicht nur filename |
| Model nicht gefunden | Dateinamen im JSON gegen models/checkpoints/ abgleichen |
Drei Endpunkte reichen für den Einstieg: POST /prompt (auslösen),
GET /history/{prompt_id} (Status) und GET /view (Bild holen). Damit lässt
sich jede GUI-Aktion automatisieren – die Grundlage für Batch-Generierung und eigene Tools.
Hat dir das Tutorial geholfen?
Die Inhalte sind kostenlos. Über einen Kaffee freue ich mich sehr. ☕