When a run went to a worker, the master's collect-only delegate prompt was queued at once. ComfyUI's single executor then sat on that prompt waiting for the worker, so every full workflow queued behind it waited too: with one worker and a participating master, the master GPU only started once the worker's first job finished. On a participating master the delegate prompt is now held back and queued only once the worker posts its final result (job_complete is_last), so the master executor keeps pulling full workflows meanwhile. If the worker is seen idle or unreachable for several probes without delivering, the delegate is queued anyway so the Collector's timeout path reports it. Both the immediate dispatch to an idle worker and dispatcher claims use this; an orchestrator-only master, which has nothing else to run, still queues the delegate immediately so the run stays visible in its queue. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
10 KiB
ComfyUI-Distributed API (Experimental)
This document describes the public HTTP API added to ComfyUI-Distributed to allow queueing distributed workflows from external tools (scripts, services, CI jobs, render farms, etc.) without using the ComfyUI web UI.
Demo
- Video walkthrough: https://youtu.be/yiQlPd0MzLk
Examples Repository
Overview
What this adds
POST /distributed/queue— queues a workflow using the same distributed orchestration rules as the UI:- Detects distributed nodes in the prompt (
DistributedCollector,UltimateSDUpscaleDistributed). - Resolves enabled/selected workers.
- Probes and dispatches workers through
/distributed/worker_wsby default. - If
settings.websocket_orchestration=false, probes withGET /promptand dispatches withPOST /promptinstead. - Queues the master workflow in ComfyUI’s prompt queue.
- If any
DistributedCollectorhasload_balance=true, runs the prompt on one free participant. When every participant is busy, the prompt waits in the master queue (under the returnedprompt_id) and is handed to whichever worker frees up first, or taken by the master when it participates. When a participating master hands a run to a worker, the master's collect-only prompt is queued only once the worker's result arrives, so the master GPU keeps running other queued prompts meanwhile; until then theprompt_idis not in/queueor/history. Prompts withUltimateSDUpscaleDistributednodes or serverless workers keep the queue-depth selection instead.
- Detects distributed nodes in the prompt (
What it does not add
- Authentication/authorization.
- A separate “job status” API for distributed results (you still use ComfyUI’s normal prompt history / websocket flow, and the existing
/distributed/queue_status/{job_id}behavior for collector queues).
Endpoint: POST /distributed/queue
Queue a workflow for distributed execution.
URL
http://<master-host>:<master-port>/distributed/queue
Headers
Content-Type: application/json
Request Body
{
"prompt": { "<node_id>": { "class_type": "...", "inputs": { } } },
"workflow": { },
"client_id": "external-client",
"delegate_master": false,
"enabled_worker_ids": ["1", "2"],
"workers": ["1", "2"],
"auto_prepare": true,
"trace_execution_id": "exec_1700000000_ab12cd"
}
Fields
prompt(required unlessworkflow.promptis present, object)- A complete ComfyUI API-format prompt graph, using the same shape as
POST /prompt. - This is not the normal visual workflow export from the ComfyUI editor.
- A complete ComfyUI API-format prompt graph, using the same shape as
workflow(optional, object)- Workflow metadata that ComfyUI normally stores in
extra_pnginfo.workflow. - If you don’t care about UI metadata, you can omit it.
- Workflow metadata that ComfyUI normally stores in
client_id(required, string)- Passed through as
extra_data.client_id(useful if you consume ComfyUI websocket events).
- Passed through as
delegate_master(optional, boolean)- If
true, attempts “workers-only” execution for workflows based onDistributedCollector. - Current limitation: delegate-only mode does not support
UltimateSDUpscaleDistributedand will fall back to running the full prompt on master.
- If
enabled_worker_ids(required, array of strings)- The explicit worker IDs to consider for this run.
workers(optional, array of strings or objects withid)- Transitional alias for
enabled_worker_idsused by older clients.
- Transitional alias for
auto_prepare(optional, boolean)- Kept for wire compatibility.
- Backend orchestration always runs with auto-prepare semantics.
- If top-level
promptis omitted, backend will attemptworkflow.prompt.
trace_execution_id(optional, string)- Passed through to orchestration logs.
- Server log lines include the marker as
[exec:<trace_execution_id>].
How to get enabled_worker_ids
Worker IDs come from the plugin config (GET /distributed/config) under workers[].id.
Example (bash + jq):
curl -s "http://127.0.0.1:8188/distributed/config" \
| jq -r '.workers[] | "id=\(.id)\tname=\(.name)\tenabled=\(.enabled)\thost=\(.host)\tport=\(.port)\ttype=\(.type)"'
Example (PowerShell):
$cfg = Invoke-RestMethod "http://127.0.0.1:8188/distributed/config"
$cfg.workers | Select-Object id,name,enabled,host,port,type | Format-Table -AutoSize
Response Body
{
"prompt_id": "<uuid>",
"worker_count": 2,
"auto_prepare_supported": true
}
prompt_id— the master prompt id queued into ComfyUI.worker_count— number of workers that received a dispatched prompt (only those that passed the health check).
Status Codes
200— queued.400— invalid JSON or invalid body.500— orchestration/dispatch failure (see server logs for details).
Worker requirements (important)
For a worker to participate, it must be reachable from the master. By default:
- WebSocket probe and dispatch:
<worker-base>/distributed/worker_wsmust accept the connection.
If settings.websocket_orchestration=false:
- Health check:
GET <worker-base>/promptmust return HTTP 200. - Dispatch:
POST <worker-base>/promptmust accept the prompt.
Also, for collector-based flows:
- Workers will send results back to the master via
POST /distributed/job_complete(that route must be reachable from workers).
Endpoint: POST /distributed/job_complete
Submit direct-worker image/audio results to the master Collector queue. The route accepts canonical JSON completions or multipart image chunks, including opt-in exact floating-point IMAGE transport. Serverless typed results use their separate bridge/provider contract.
URL
http://<master-host>:<master-port>/distributed/job_complete
JSON request body
{
"job_id": "exec_1234567890_17",
"worker_id": "worker-1",
"batch_idx": 0,
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
"is_last": false
}
Canonical envelope (required fields)
job_id(string, required)worker_id(string, required)batch_idx(integer >= 0, required)image(string, optional ifaudiois present)- PNG payload as either:
- data URL:
data:image/png;base64,... - raw base64 PNG bytes
- data URL:
- PNG payload as either:
is_last(boolean, required)
At least one of image or audio must be present. Optional audio is an object
with sample_rate (positive integer), shape ([batch, channels, samples]),
dtype: "float32", and data (base64 waveform bytes). Audio-only completions
still include batch_idx; the Collector uses 0 and is_last: true.
Multipart image chunks
Send multipart/form-data with a JSON envelope field and repeated binary
media file fields. The envelope contains job_id, worker_id, format,
start_index (nonnegative integer), count, width, height, is_last, and
optional canonical audio.
png,jpeg,webp: onemediapart per frame.ffv1,h264,h265(experimental),av1: one video part per chunk.exact: one raw tensor part per frame and an envelopedescriptorslist with onetensor-le-v1IMAGE descriptor per part. Each descriptor shape must be[1, height, width, channels]; decoding preserves supported float dtype and values instead of quantizing to 8-bit.
legacy_png uses JSON, not multipart. Exact transport requires support on both
sides and never retries as an 8-bit format when an older master rejects it.
See result transfer for format controls,
non-exact compatibility fallbacks and request limits.
CORS note
If you call the API from a browser (not from a backend), ensure the master ComfyUI is started with --enable-cors-header.
Log Endpoints
GET /distributed/worker_log/{worker_id}
Read log files for workers launched locally by the master UI process manager.
- Intended for managed local workers.
- Query param:
lines(optional, default1000).
GET /distributed/local_log
Read this ComfyUI instance's in-memory runtime log buffer.
- Available on any ComfyUI-Distributed instance (master or worker).
- Query param:
lines(optional, default300, max3000).
GET /distributed/remote_worker_log/{worker_id}
Proxy endpoint on master that fetches logs from a configured remote/cloud worker's
/distributed/local_log.
- Intended for remote/cloud workers in master config.
- Query param:
lines(optional, default300, max3000).
Examples
1) Minimal curl request envelope
curl -X POST "http://127.0.0.1:8188/distributed/queue" \
-H "Content-Type: application/json" \
-d @payload.json
payload.json must contain a complete ComfyUI API-format prompt. The abbreviated envelope below illustrates the request shape but is not directly executable:
{
"prompt": {
"<node_id>": {
"class_type": "<node class>",
"inputs": {"<required_input>": "<value or connection>"}
}
},
"enabled_worker_ids": [],
"client_id": "external-client"
}
Export or construct a valid API-format prompt with all required node inputs and at least one output node before submitting it.
2) Python (requests)
import requests
url = "http://127.0.0.1:8188/distributed/queue"
payload = {
"prompt": {...},
"workflow": {...},
"client_id": "external-client",
"delegate_master": False,
"enabled_worker_ids": ["1", "2"],
}
r = requests.post(url, json=payload, timeout=60)
r.raise_for_status()
print(r.json())
3) JavaScript (fetch)
const url = "http://127.0.0.1:8188/distributed/queue";
const payload = {
prompt: {/* ... */},
workflow: {/* ... */},
client_id: "external-client",
delegate_master: false,
enabled_worker_ids: ["1", "2"],
};
const resp = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!resp.ok) throw new Error(await resp.text());
console.log(await resp.json());
Operational notes / gotchas
- If the workflow contains no distributed nodes, the endpoint falls back to normal master queueing and returns
worker_count: 0. - Worker selection is “best-effort”: offline workers are skipped.
- For public URLs/tunnels: prefer configuring
master.hostwith an explicit scheme (https://...) to avoid ambiguity.
Changelog (this feature)
- Added
POST /distributed/queueendpoint. - Added orchestration module used by the endpoint.