Three common patterns for calling the ComfyUI Server API
This page demonstrates three ways to interact with the ComfyUI Server API, from a simple HTTP submission to a full WebSocket integration with real-time image output.All examples use the default SD1.5 workflow for illustration. Before using the API, you need to export your workflow in API format.
These examples use Python with the standard library and the websocket-client package (pip install websocket-client). The underlying API protocol is the same regardless of language — see the Cloud API Reference for TypeScript and curl equivalents.
Source: basic_api_example.pyThe simplest approach: submit a workflow and don’t wait for results. Useful for fire-and-forget jobs where you check outputs later.
This method uses the SaveImage node, which saves images to disk on the server. To retrieve them, you’d need to follow up with a call to GET /view?filename=....
Method 2: WebSocket + History (Monitor Completion)
Source: websockets_api_example.pyUse WebSocket to wait for execution to finish, then retrieve outputs via the /history endpoint. This is the recommended pattern for most use cases.
"""websockets_api_example.py — Monitor execution via WebSocket, download via /history."""import websocket # pip install websocket-clientimport uuidimport jsonimport urllib.requestimport urllib.parseSERVER_ADDRESS = "127.0.0.1:8188"client_id = str(uuid.uuid4())def queue_prompt(prompt, prompt_id): p = {"prompt": prompt, "client_id": client_id, "prompt_id": prompt_id} data = json.dumps(p).encode("utf-8") req = urllib.request.Request( f"http://{SERVER_ADDRESS}/prompt", data=data ) urllib.request.urlopen(req)def get_image(filename, subfolder, folder_type): params = urllib.parse.urlencode({ "filename": filename, "subfolder": subfolder, "type": folder_type, }) with urllib.request.urlopen( f"http://{SERVER_ADDRESS}/view?{params}" ) as response: return response.read()def get_history(prompt_id): with urllib.request.urlopen( f"http://{SERVER_ADDRESS}/history/{prompt_id}" ) as response: return json.loads(response.read())def get_images(ws, prompt): prompt_id = str(uuid.uuid4()) queue_prompt(prompt, prompt_id) while True: out = ws.recv() if isinstance(out, str): message = json.loads(out) if message["type"] == "executing": data = message["data"] if data["node"] is None and data["prompt_id"] == prompt_id: break # Execution done # Binary frames are preview images — skip them here continue history = get_history(prompt_id)[prompt_id] output_images = {} for node_id in history["outputs"]: node_output = history["outputs"][node_id] images_output = [] if "images" in node_output: for image in node_output["images"]: image_data = get_image( image["filename"], image["subfolder"], image["type"] ) images_output.append(image_data) output_images[node_id] = images_output return output_imagesif __name__ == "__main__": prompt_text = """{ "3": { ... }, "4": { ... }, "5": { ... }, "6": { ... }, "7": { ... }, "8": { ... }, "9": { "class_type": "SaveImage", "inputs": { ... } } }""" prompt = json.loads(prompt_text) prompt["3"]["inputs"]["seed"] = 5 prompt["6"]["inputs"]["text"] = "masterpiece best quality man" ws = websocket.WebSocket() ws.connect(f"ws://{SERVER_ADDRESS}/ws?clientId={client_id}") images = get_images(ws, prompt) ws.close() print(f"Got {len(images)} output node(s) with images.") # Display the images (requires Pillow): # for node_id in images: # for image_data in images[node_id]: # from PIL import Image # import io # img = Image.open(io.BytesIO(image_data)) # img.show()
The WebSocket binary frames contain in-progress preview images during generation. You can decode them for live previews (see the Server Messages page for the binary format).
Method 3: WebSocket with SaveImageWebsocket (Real-time Images)
Source: websockets_api_example_ws_images.pyFor scenarios where you don’t want images saved to disk, use the SaveImageWebsocket node. Images are delivered directly via WebSocket binary frames.
"""websockets_api_example_ws_images.py — Receive images directly via WebSocket."""import websocket # pip install websocket-clientimport uuidimport jsonimport urllib.requestimport urllib.parseSERVER_ADDRESS = "127.0.0.1:8188"client_id = str(uuid.uuid4())def queue_prompt(prompt): p = {"prompt": prompt, "client_id": client_id} data = json.dumps(p).encode("utf-8") req = urllib.request.Request( f"http://{SERVER_ADDRESS}/prompt", data=data ) return json.loads(urllib.request.urlopen(req).read())def get_images(ws, prompt): prompt_id = queue_prompt(prompt)["prompt_id"] output_images = {} current_node = "" while True: out = ws.recv() if isinstance(out, str): message = json.loads(out) if message["type"] == "executing": data = message["data"] if data["prompt_id"] == prompt_id: if data["node"] is None: break # Execution done current_node = data["node"] else: # Binary frame — image data from SaveImageWebsocket if current_node == "save_image_websocket_node": images_output = output_images.get(current_node, []) # The first 8 bytes are type/meta, rest is image data images_output.append(out[8:]) output_images[current_node] = images_output return output_imagesif __name__ == "__main__": prompt_text = """{ "3": { "class_type": "KSampler", "inputs": { ... } }, ... "save_image_websocket_node": { "class_type": "SaveImageWebsocket", "inputs": {"images": ["8", 0]} } }""" prompt = json.loads(prompt_text) prompt["3"]["inputs"]["seed"] = 5 ws = websocket.WebSocket() ws.connect(f"ws://{SERVER_ADDRESS}/ws?clientId={client_id}") images = get_images(ws, prompt) ws.close() print(f"Received {len(images)} image(s) via WebSocket.") # Display (requires Pillow): # for image_data in images.get("save_image_websocket_node", []): # from PIL import Image # import io # img = Image.open(io.BytesIO(image_data)) # img.show()
The workflow must use a node with class_type: "SaveImageWebsocket" (a built-in node) instead of the regular SaveImage node.