ComfyUI API Tutorial 2026: Automate Image Generation ComfyUI's built-in HTTP server on port 8188 accepts workflow JSON in API format, enabling image generation automation without the GUI. Developers can queue prompts, poll for completion, and download results using under 50 lines of Python. The API format strips GUI metadata, using numeric node IDs and connection arrays for efficient pipeline integration. This article was originally published on aifoss.dev TL;DR : ComfyUI's built-in HTTP server accepts workflow JSON on port 8188 — the same JSON the GUI exports when you click "Save API Format ". You can queue a prompt, poll for completion, and download the result in under 50 lines of Python. No GUI, no browser tab, no manual clicking. What you'll have running after this guide: The GUI is fine for building and testing workflows. It's a problem once you need more than a few images, or need to integrate generation into a pipeline. Common use cases where the API makes sense: If you're generating one image at a time and want to tweak nodes visually, stay in the GUI. The API is for automation. pip install requests websocket-client If you haven't installed ComfyUI yet, the ComfyUI review https://dev.to/blog/comfyui-review-2026/ covers installation from scratch. For GPU hardware requirements, the Stable Diffusion 8GB VRAM guide https://dev.to/blog/stable-diffusion-8gb-vram-guide-2026/ has a good breakdown of what each model tier needs. By default, ComfyUI only listens on 127.0.0.1 — accessible only from the same machine. That's fine if you're scripting locally. For a remote GPU box or Docker container, add --listen : Local access only same machine python main.py --port 8188 All interfaces for remote scripting or Docker python main.py --listen 0.0.0.0 --port 8188 With VRAM optimization for 8–12 GB cards python main.py --listen 0.0.0.0 --port 8188 --lowvram Once running, verify it's up: curl http://127.0.0.1:8188/system stats {"system": {"os": "posix", "python version": "3.10.x", ...}} The server starts a REST API and a WebSocket server on the same port. There is no authentication by default — if you expose this to a network, use a firewall rule or reverse proxy with auth. The GUI workflow JSON and the API workflow JSON are different formats. The GUI format includes node positions, colors, and UI state. The API format is stripped down to just inputs and connections — which is all the server needs. To export: workflow api.json The API format uses numeric string keys "1" , "2" , "3" as node IDs. Each node has a class type and an inputs object. Connections between nodes are expressed as "source node id", output index arrays rather than named references. Here is a minimal SDXL baseline workflow in API format: { "4": { "class type": "CheckpointLoaderSimple", "inputs": { "ckpt name": "sd xl base 1.0.safetensors" } }, "5": { "class type": "EmptyLatentImage", "inputs": { "batch size": 1, "height": 1024, "width": 1024 } }, "6": { "class type": "CLIPTextEncode", "inputs": { "clip": "4", 1 , "text": "a photorealistic red fox in snow, golden hour lighting" } }, "7": { "class type": "CLIPTextEncode", "inputs": { "clip": "4", 1 , "text": "blurry, low quality, watermark, text" } }, "3": { "class type": "KSampler", "inputs": { "cfg": 7.0, "denoise": 1.0, "latent image": "5", 0 , "model": "4", 0 , "negative": "7", 0 , "positive": "6", 0 , "sampler name": "euler", "scheduler": "normal", "seed": 42, "steps": 20 } }, "8": { "class type": "VAEDecode", "inputs": { "samples": "3", 0 , "vae": "4", 2 } }, "9": { "class type": "SaveImage", "inputs": { "filename prefix": "api output", "images": "8", 0 } } } For SDXL specifically, node "4" connects to both CLIP encoders and the KSampler via its three outputs: 0 = model, 1 = CLIP, 2 = VAE. The connection syntax "4", 1 means "output index 1 of node 4." python import uuid import json import requests SERVER = "127.0.0.1:8188" CLIENT ID = str uuid.uuid4 def queue prompt workflow: dict - str: """Submit a workflow. Returns the prompt id for tracking.""" payload = {"prompt": workflow, "client id": CLIENT ID} r = requests.post f"http://{SERVER}/prompt", json=payload r.raise for status return r.json "prompt id" The client id is a UUID you generate once per session. It ties your WebSocket connection to your HTTP requests so the server routes status messages back to you. You can skip it for pure HTTP polling, but you need it for WebSocket tracking. The server responds immediately with {"prompt id": "