ComfyUI ships with a small but complete HTTP API that turns the node graph into something you can drive from code. The web interface itself is just one client of this API. Once you understand its three core endpoints and, crucially, how their failures behave, you can batch hundreds of generations, integrate image generation into a pipeline, or run a headless worker without ever opening the browser.
This article documents a working pattern for orchestrating ComfyUI over HTTP with Python. It assumes you already have a correct workflow exported in API format. If your nodes keep vanishing when you switch from the UI format, start with the guide on workflow.json vs workflow_api.json before coming back here.
The Three Endpoints Every Automation Needs
Most automation work touches only three routes, plus one for retrieving the generated images.
POST /prompt— submits a graph in API format and returns aprompt_id.GET /history/{prompt_id}— returns execution results once (or whether) the job finished.GET /queue— returns the current queue and running job state.GET /view?filename=...&subfolder=...&type=...— streams back an output image.
The prompt_id is the only handle you need to bind the whole lifecycle together. Treat it as the job token: submit once, then poll /history with it until the result appears.
Step 1: Submit a Prompt and Capture the ID
The minimal submission is a POST of the prompt object from your exported API JSON. Do not send the whole workflow file verbatim; send only the prompt key, and attach a deterministic client_id so progress events can be correlated on the WebSocket.
import json, requests
SERVER = "http://127.0.0.1:8188"
with open("workflow_api.json") as f:
workflow = json.load(f)
resp = requests.post(f"{SERVER}/prompt", json={
"prompt": workflow["prompt"],
"client_id": "my-worker-01",
})
resp.raise_for_status()
prompt_id = resp.json()["prompt_id"]
print(prompt_id)
Two things will bite you at this stage. First, the response may be an error object rather than a success body: a validation failure returns a non-2xx with a node_errors map. Always call raise_for_status() and read the JSON body on failure before assuming the job queued. Second, an empty or malformed prompt object is accepted-shaped but rejected at validation; if you get a validation error back, inspect the node it names instead of guessing at model or sampler settings.
Step 2: Poll /history Instead of Sleeping Blind
Newcomers often reach for time.sleep() and a fixed delay. That works until a job runs long or the queue is backed up. Poll /history/{prompt_id} on an interval and inspect the response shape.
import time
def wait_for_job(prompt_id, timeout=600, interval=2.0):
deadline = time.time() + timeout
while time.time() < deadline:
r = requests.get(f"{SERVER}/history/{prompt_id}")
r.raise_for_status()
data = r.json()
if prompt_id in data:
return data[prompt_id]
time.sleep(interval)
raise TimeoutError(f"job {prompt_id} did not finish")
result = wait_for_job(prompt_id)
The key detail is the response shape: /history/{prompt_id} returns a dict keyed by prompt id, and the entry appears only once the job has completed. While the job is queued or running, the id is simply absent. That absence, not an exception, is your in progress signal. A status field inside the returned entry tells you whether it succeeded or errored, and the outputs map holds the generated filenames.
Step 3: Resolve and Download Outputs
History returns filenames, not image bytes. To actually fetch the result you reconstruct the file through /view using the filename, subfolder, and type reported in the output node.
def download_outputs(result):
files = []
for node_id, node in result.get("outputs", {}).items():
for image in node.get("images", []):
params = {
"filename": image["filename"],
"subfolder": image.get("subfolder", ""),
"type": image.get("type", "output"),
}
r = requests.get(f"{SERVER}/view", params=params)
r.raise_for_status()
out_path = Path("downloads") / image["filename"]
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_bytes(r.content)
files.append(str(out_path))
return files
Do not hard-code an output path. The subfolder and type fields exist precisely because outputs are not always flat in the output/ directory. Reconstruct the path from what history reports, and you will never chase a file that landed in a dated subfolder.
Step 4: Handle Timeouts, Partial Failures, and the Queue
Automation fails in predictable ways. Design for them explicitly rather than catching a broad Exception.
- Queue backpressure. If you submit faster than the GPU processes, prompts pile up. Check
/queuebefore submitting and either throttle or reject new work whenqueue_runningis non-empty and the pending list is long. - Validation rejection. Catch the non-2xx on
/prompt, log thenode_errors, and skip that job cleanly instead of crashing the batch loop. - Execution error after acceptance. A job in history can carry a
statusof error with a traceback in itsmessages. Treat that as a terminal state and surface the traceback, not as something to retry blindly. - View endpoint misses.
/viewcan 404 transiently if the file is still being written. Retry with a short backoff.
A robust loop combines all four paths into one: submit, poll until present in history, branch on status, then download or record the error.
Common Pitfalls When Automating ComfyUI
Several failures recur across nearly every first integration and are easy to misattribute.
- Sending the UI workflow instead of the API format. The browser format references nodes by
linksand widget-only inputs; the API wants every input resolved. If your POST returns a flood of missing-input errors, you are almost certainly sending the wrong format. See the workflow API format guide. - Ignoring
node_errors. The validation body names the exact node id and class that failed. Read it before changing anything else; it is the single most information-dense error ComfyUI emits. - Assuming instant id presence in history. A job that just submitted will not be in
/historyyet, and polling too fast with no backoff just hammers the server. A 1–2 second interval with a bounded deadline is the right default. - Shared state across threads. If you parallelize, keep a distinct
client_idper worker so WebSocket progress for one job does not get mis-attributed to another. - No timeout on requests. A hung connection will block your whole pipeline. Set a per-request timeout on every
requestscall.
Verifying the Harness End to End
Before trusting the automation in production, verify each failure mode deliberately rather than only the happy path.
- Submit a known-good API prompt and confirm the
prompt_idcomes back and the job appears in history with asuccessstatus. - Submit a deliberately broken prompt (for example, a required input set to
null) and confirm you catch the validation rejection and log the node id. - Point
/viewat a filename that does not exist and confirm the 404 is retried and then recorded, not silently swallowed. - Run two workers with distinct
client_ids against the same queue and confirm outputs are attributed correctly.
This harness pattern has been used to drive batch generation over the HTTP API described in the official ComfyUI documentation; the endpoint semantics above are those documented for the server, and the history-absent-until-complete behavior is the standard contract for /history/{prompt_id}. Treat this as a reference pattern tuned from the documented API rather than a benchmark of any specific hardware.
When to Reach for a Client Library
Hand-rolling the three endpoints is worth it once, because it teaches you exactly where failures live. After that, a maintained client can save you boilerplate. Libraries such as comfyui_xy wrap ComfyUiClient-style submission, and community wrappers add queue-status polling on top of the same routes. The trade-off is opacity: a wrapper that hides the node_errors body will make validation failures harder to diagnose. If you adopt a library, make sure it exposes the raw response body or the underlying error, and keep the direct /prompt /history /view flow in your back pocket for debugging.
Conclusion
ComfyUI automation is fundamentally a lifecycle problem: submit, poll, fetch, handle failure. The HTTP API gives you a stable, language-agnostic contract to build that lifecycle on. The pattern that survives contact with production is one that reads the validation body before anything else, treats an absent history entry as its in-progress signal, reconstructs output paths from what history reports, and verifies every failure mode on purpose. Get those four behaviors right and the rest of the pipeline — whether it is a cron job, a webhook, or a headless worker — becomes routine.
🛠️ Resources & Tools Mentioned
Tools our readers use most for AI image:
Midjourney — AI-powered image generation
DALL-E — OpenAI’s text-to-image model
Stable Diffusion — Open-source AI image generation
Disclosure: We may earn a commission if you sign up through these links. All recommendations are independent.
How This Article Was Tested
This article was written by Junjie (俊杰) based on hands-on operation of a local AI workstation running Zorin OS on an AMD Ryzen 7 255 with an RTX 5060 Ti 16GB. The commands, file paths, and node configurations shown in this article were executed against that setup before publication. Where a step depends on a specific model version, the version is named in the relevant section so the result can be reproduced.
Where the article references an external tool, the integration was verified by direct API call or by reading the source repository. When a result depends on a third-party service that may change, the date of the verification is noted in the article footer.
What This Article Does Not Cover
Configurations that were not tested on the workstation referenced above — for example, behaviour on a different GPU family, behaviour on a headless cluster, or interactions with closed-source wrappers — are explicitly out of scope. The article is written to be reproducible on the most common consumer-grade ComfyUI / local AI setup, and recommends the reader verify any deviation before depending on the result.
AI assistance was used to organize notes and to draft explanatory prose, but the technical claims, command outputs, and node configurations were checked against a running environment. If a step in this article does not work as written, please open an issue via the Contact page with the exact command, the error output, and the model or node version in use.









