Once you take ComfyUI out of the browser and drive it from a script, a simple job that you expected to take thirty seconds can turn into a queue full of runaway video renders. The HTTP API exposes five different ways to stop work, and they operate on different parts of the system. Calling the wrong one does nothing visible, or worse, interrupts a job you actually wanted to keep. This guide walks through each endpoint, what it does to your queue, and the race condition that made job cancellation buggy until recently.
The Three Jobs States Every Endpoint Hinges On
Before choosing an endpoint, you need to know which of three states a prompt is in. ComfyUI tracks a prompt from the moment you submit it until it lands in history, and each state is controlled by a different route.
- Running — the prompt is currently executing on the GPU. Exactly one prompt can be running at a time in the default single-GPU server.
- Pending — the prompt is sitting in the queue, waiting for its turn after the running prompt finishes.
- Finished — the prompt has completed (or errored) and moved into history.
This distinction matters because the endpoint that stops a running prompt is completely different from the endpoint that removes a pending one. A prompt does not magically move between these buckets when you call the wrong route — it just keeps going.
You can see a snapshot of the first two states at any time with GET /queue. The response body has two lists: queue_running (the current prompt) and queue_pending (everything waiting). Each entry carries the prompt’s UUID, which is the handle you’ll use for every cancellation call below.
/interrupt Stops Only What Is Running
POST /interrupt is the bluntest tool and the one most people reach for first. In its simplest form, it stops whatever prompt is currently executing:
import requests
requests.post("http://127.0.0.1:8188/interrupt")
That global interrupt halts the active prompt mid-sampling and moves it into history in an interrupted state. It does nothing to the pending queue — the next queued prompt starts running immediately after the interrupt completes. If that is not what you want, you must also clear the pending queue (see the next section).
The newer and safer form accepts a prompt_id in the JSON body. This was added in PR #9607 specifically to close a race condition: without it, a timed interrupt could land on a different prompt that happened to start in the gap between your queue snapshot and your interrupt call. When you pass a prompt_id, the server checks whether that exact prompt is the one currently running and only interrupts if it is:
requests.post(
"http://127.0.0.1:8188/interrupt",
json={"prompt_id": "1f2a3b4c-..."},
)
If the prompt is no longer running, the server logs “prompt is not currently running, skipping interrupt” and does nothing — which is exactly the behaviour you want for a targeted cancel. The trade-off is that this only ever touches the running prompt; it cannot reach anything pending.
/queue Removes Pending Jobs and Can Wipe Everything
For anything waiting in line, the route is POST /queue. It accepts a JSON body with either of two keys.
To remove specific pending prompts, pass a list of prompt_id values under delete:
requests.post(
"http://127.0.0.1:8188/queue",
json={"delete": ["id-1", "id-2"]},
)
To clear the entire pending queue in one shot, use clear:
requests.post(
"http://127.0.0.1:8188/queue",
json={"clear": True},
)
A critical detail: /queue operates only on the pending list. If a prompt has already started running, deleting it from the queue does not stop the GPU work — the running prompt is not in the pending list at all. This is the single most common mistake in automation scripts: you call /queue delete on a prompt that just started, the call returns 200, and nothing appears to happen because the prompt is now running, not pending. To be safe, treat /queue delete and /interrupt as a pair rather than alternatives.
/history Cleans Up Completed and Interrupted Records
Once a job is finished — whether it completed normally, errored, or was interrupted — it lives in history. History entries are no longer on the GPU and do not block anything, but they still occupy server memory and clutter the GET /history output your script reads back. ComfyUI gives you a POST /history route to tidy up:
# delete specific history entries
requests.post(
"http://127.0.0.1:8188/history",
json={"delete": ["id-1"]},
)
# wipe all history
requests.post(
"http://127.0.0.1:8188/history",
json={"clear": True},
)
The body shape mirrors /queue exactly, which makes the API easy to remember: delete accepts a list of ids, clear nukes everything. The key conceptual point is that /history is housekeeping, not cancellation. Do not reach for it when a job is still running or pending; it will not stop anything — it only removes records after the fact.
The New /api/jobs/cancel Endpoint Is the Upgrade You Want
For a long time, the “cancel one job no matter what state it’s in” use case had no clean answer. You had to snapshot /queue, figure out whether your target prompt_id was running or pending, then branch into /interrupt or /queue delete. The race condition in the middle was the source of the feature request captured in discussion #8835 — deleting a job from the frontend could silently fail because the job had already started running between your click and the request.
ComfyUI now ships a state-agnostic, idempotent cancellation route that collapses all of that logic:
requests.post("http://127.0.0.1:8188/api/jobs/1f2a3b4c-.../cancel")
Under the hood it resolves the prompt’s state and does the right thing automatically: it interrupts if the job is running, dequeues if it is pending, and returns a benign {"cancelled": false} if the job already finished or the id is unknown. Because it is idempotent, you can safely fire it at a job you are no longer sure is still in flight without risking an error or interrupting the wrong thing.
There is also a batch variant for multi-job teardown:
requests.post(
"http://127.0.0.1:8188/api/jobs/cancel",
json={"job_ids": ["id-1", "id-2", "id-3"]},
)
The batch version is best-effort and idempotent too: each well-formed id is cancelled if it is running or pending, while finished or unknown ids are treated as no-ops rather than errors. A batch that is entirely no-ops still returns 200 with {"cancelled": false}. This is the endpoint to standardise on for any “cancel everything I just submitted” workflow.
One compatibility note worth checking against your install: all of these routes are also available under an /api prefix (/api/interrupt, /api/queue, /api/history, and /api/jobs/.../cancel). The server auto-registers both the unprefixed and prefixed paths, so either spelling works — but if you reverse-proxy ComfyUI behind a gateway that routes on the /api prefix, using the prefixed forms keeps traffic on the correct path. The /api/jobs/* cancellation routes specifically live under the /api prefix.
A Careful Cancel Routine That Never Interrupts the Wrong Job
Putting the pieces together, here is a small, defensible pattern for cancelling one job by its prompt_id without collateral damage:
import requests
BASE = "http://127.0.0.1:8188"
def cancel_job(prompt_id: str) -> bool:
r = requests.post(f"{BASE}/api/jobs/{prompt_id}/cancel")
r.raise_for_status()
return r.json().get("cancelled", False)
For a teardown in a script that might have queued several jobs, snapshot the id list first, then cancel the whole batch and clear history afterward:
ids = [...] # prompt_ids you submitted
requests.post(f"{BASE}/api/jobs/cancel", json={"job_ids": ids})
requests.post(f"{BASE}/history", json={"clear": True})
Two habits will save you from most “the cancel didn’t work” reports. First, always capture the prompt_id from the POST /prompt response — it is the only stable handle across state transitions. Second, after cancelling, poll GET /queue and GET /history to confirm the prompt actually left the queue and did not silently restart, rather than trusting the HTTP 200 from the cancel call alone. See our guide on websocket events and /history polling for a deeper look at confirming job completion by state.
Conclusion
ComfyUI’s job-control API is small but easy to fumble if you conflate the five routes. Remember the mapping: /interrupt stops the single running prompt (optionally targeted by prompt_id), /queue deletes pending jobs or clears the whole backlog, /history deletes finished records, and the newer /api/jobs/{id}/cancel (plus its batch form) collapses state detection into one idempotent call. For new automation, prefer /api/jobs/{id}/cancel and keep the targeted /interrupt form for the narrow case where you must be certain you are stopping a specific in-flight prompt. Validate against the official ComfyUI server routes documentation and the exact version you have installed, since these cancellation helpers have landed progressively across recent releases.
Leave a Reply