ComfyUI API는 어디에 있나 — 화면도 같은 서버의 손님
ComfyUI를 켜면 따로 무엇을 하지 않아도 HTTP 서버가 함께 뜬다. 공식 문서의 서버 개요는 ComfyUI가 실행되면 http://127.0.0.1:8188에서 HTTP 서버를 자동으로 시작한다고 적었다. 브라우저로 여는 그 주소가 바로 API의 주소다. 소스 코드의 시작 옵션도 같다. comfy/cli_args.py에서 --listen의 기본값은 127.0.0.1, --port의 기본값은 8188이다.
127.0.0.1은 «이 PC 자신»을 뜻하는 주소다. 이 주소로만 열려 있으면 같은 PC 안의 프로그램만 서버에 닿는다. 화면을 그리는 브라우저도, 이제 만들 파이썬 스크립트도 같은 PC 안의 손님이라 이 주소로 충분하다. 브라우저를 띄우지 않고 서버만 켜 두고 싶다면 --disable-auto-launch를 붙인다.
화면이 하는 일을 스크립트가 대신한다
공식 문서는 화면이 서버에 요청을 넣을 때 위젯 값을 포함한 워크플로 전체를 보낸다고 설명한다. 요청을 대기열에 넣은 뒤 화면에서 바꾼 값은 서버가 받지 않는다는 말도 덧붙였다. 서버 입장에서 보면 실행 버튼은 «지금 화면의 그래프를 JSON으로 바꿔 /prompt로 한 번 보내기»일 뿐이다. 스크립트가 같은 JSON을 같은 경로로 보내면 서버는 둘을 구별하지 않고 같은 대기열에 넣는다.
서버가 열어 둔 경로는 server.py에서 @routes를 찾으면 모두 보인다. 공식 문서의 경로 표에서 이 글이 쓰는 것만 추리면 다음과 같다.
| 경로 | 방식 | 하는 일 |
|---|---|---|
/prompt | POST | 워크플로를 검증해 대기열에 넣음 |
/ws | 웹소켓 | 진행 · 끝 알림을 실시간으로 받음 |
/history/{prompt_id} | GET | 끝난 작업의 기록(결과 파일 이름 포함) |
/view | GET | 결과 그림 파일 받기 |
/queue | GET · POST | 대기열 보기 · 비우기 · 하나 빼기 |
/interrupt | POST | 지금 실행 중인 작업 멈추기 |
이 밖에 노드 종류의 입력 목록을 돌려주는 /object_info, 파이썬 판 · 그래픽카드 · VRAM을 알려 주는 /system_stats도 있다. 스크립트가 서버가 켜져 있는지부터 확인하고 싶다면 /system_stats를 한 번 열어 보면 된다.
--listen은 조심해서 다룬다. 값 없이 --listen만 붙이면 0.0.0.0,::, 곧 이 PC의 모든 네트워크 주소로 열린다. 같은 공유기에 붙은 다른 기기에서도 이 PC의 ComfyUI에 그림을 넣고 결과를 받아 갈 수 있게 된다는 뜻이다. 이 글의 스크립트는 같은 PC 안에서만 부르므로 --listen을 바꿀 일이 없다. 기본값 그대로 둔다.
첫 요청 — API 형식 JSON을 /prompt로 보내기
스크립트가 보낼 JSON은 화면에서 만든다. 공식 문서는 워크플로 저장 형식이 두 가지라고 설명한다. 화면에서 다시 열기 위한 저장 형식(File → Save, Ctrl+S)과, 프로그램이 서버에 보내기 위한 API 형식(File → Export Workflow (API))이다. API 형식은 위치 · 크기 · 색 · 그룹 같은 화면 정보를 빼고 노드 번호를 키로 쓴다. 저장 형식 파일밖에 없다면 화면에서 열고 Export Workflow (API)로 다시 내보내면 된다. 두 형식이 그림 파일 안에 어떻게 함께 들어가는지는 워크플로 불러오기 편에 정리했다.
API 형식 JSON은 생김새가 단순하다. 맨 바깥 키가 노드 번호("3", "4" …)이고, 각 노드에는 노드 종류를 적은 class_type과 입력값을 담은 inputs가 있다. 입력값 가운데 ["4", 0]처럼 생긴 것은 «4번 노드의 0번째 출력»으로 이어진 연결이고, 숫자나 글자는 칸에 적은 값이다. 내보낸 파일에는 화면의 노드 제목을 담은 _meta.title도 붙는다. 프롬프트 글은 CLIPTextEncode 노드의 text, 시드는 KSampler 노드의 seed에 들어 있다.
가장 짧은 요청과 서버의 대답
저장소의 basic_api_example.py가 가장 짧은 형태다. 기본 워크플로의 API 형식 JSON을 문자열로 넣어 두고, 6번 노드의 글과 3번 노드의 시드를 바꾼 뒤 {"prompt": 워크플로}를 http://127.0.0.1:8188/prompt로 POST한다. 핵심은 아래 몇 줄이다.
import json
from urllib import request
prompt = json.load(open("workflow_api.json", encoding="utf-8"))
prompt["6"]["inputs"]["text"] = "a glass teapot on a wooden table"
prompt["3"]["inputs"]["seed"] = 5
data = json.dumps({"prompt": prompt}).encode("utf-8")
res = request.urlopen(request.Request("http://127.0.0.1:8188/prompt", data=data))
print(json.loads(res.read()))서버는 받은 JSON을 바로 실행하지 않고 먼저 검증한다. 공식 문서는 /prompt가 워크플로를 검증해 실행 대기열에 넣고, 대기열 위치를 뜻하는 number와 prompt_id를 돌려주거나 검증에 실패하면 error와 node_errors를 돌려준다고 적었다. server.py를 보면 성공은 200 응답에 prompt_id · number · node_errors, 실패는 400 응답에 error · node_errors다. 파이썬의 urllib는 400 응답을 예외(HTTPError)로 던지므로, 예외 안의 본문을 읽어야 무엇이 틀렸는지 보인다.
검증 실패의 error.type으로 자주 만나는 값은 execution.py에 정해져 있다.
- prompt_outputs_failed_validation — 출력 노드로 가는 길에 틀린 입력이 있다. 자세한 원인은 node_errors에 노드 번호별로 들어 있고, 노드마다 errors · dependent_outputs · class_type 세 칸이 있다.
- value_not_in_list — 고르는 칸에 목록에 없는 값이 들어갔다. 스크립트에 적은 체크포인트 파일 이름이 실제 폴더의 이름과 다를 때 가장 흔하다. 문구를 읽는 법은 오류 메시지 읽는 법 편과 같다.
- prompt_no_outputs — Save Image 같은 출력 노드가 하나도 없다.
- missing_node_type — class_type에 적힌 노드를 서버가 모른다. 커스텀 노드가 깔리지 않은 PC에서 남의 JSON을 보낼 때 난다.
요청에 prompt_id를 직접 붙여 보낼 수도 있다. 웹소켓 예제가 그렇게 한다. 다만 server.py는 이 값이 소문자 하이픈 형식의 UUID가 아니면 invalid_prompt_id로 거절하므로, 직접 만들 때는 파이썬 uuid.uuid4()를 문자열로 바꿔 쓴다. 붙이지 않으면 서버가 만들어 응답에 담아 준다.
인용 — ComfyUI 공식 문서. ComfyUI Server Routes«When you submit a workflow in the web client, it is posted to /prompt which validates the prompt and adds it to an execution queue, returning either a prompt_id and number (the position in the queue), or error and node_errors if validation fails.» — 화면에서 워크플로를 실행하면 /prompt로 보내지고, /prompt는 그것을 검증해 실행 대기열에 넣은 뒤 prompt_id와 number(대기열 위치)를, 검증에 실패하면 error와 node_errors를 돌려준다.
화면의 실행 버튼과 스크립트의 POST가 같은 문으로 들어간다는 한 줄이다. 그래서 화면에서 잘 돌던 워크플로라면 API 형식으로 내보낸 JSON도 같은 검증을 통과한다.
끝날 때까지 기다리고 그림 받기 — 웹소켓과 /history
/prompt의 200 응답은 «대기열에 들어갔다»는 뜻이지 «그림이 나왔다»는 뜻이 아니다. 스크립트는 끝나기를 기다렸다가 결과 파일을 받아야 한다. 공식 API 예제 쪽은 이를 위한 방법 셋을 나란히 놓았고, 그 가운데 웹소켓으로 끝을 기다린 뒤 /history로 결과를 받는 두 번째 방법을 «Recommended», 곧 대부분의 경우에 권하는 방법으로 표시했다.
웹소켓 예제(websockets_api_example.py)의 흐름은 이렇다. 먼저 uuid.uuid4()로 client_id를 하나 만들고 ws://127.0.0.1:8188/ws?clientId=…로 접속한다. 같은 client_id를 /prompt 요청에 함께 실으면, 서버는 그 작업의 진행 메시지를 이 접속으로 보낸다. 스크립트는 메시지를 하나씩 받다가 종류가 executing이고 node가 None이며 prompt_id가 자기 것인 메시지가 오면 반복을 멈춘다. 공식 문서의 메시지 표도 executing의 node가 None이면 실행이 끝났다는 뜻이라고 적었다. 글자가 아닌 바이너리 메시지는 생성 중 미리 보기 그림이라 이 예제는 건너뛴다.
웹소켓을 쓰려면 파이썬 패키지 websocket-client가 따로 필요하다. 2026년 10월 28일 PyPI 기준 최신은 1.9.2이고 Python 3.10 이상을 요구한다. 예제 코드의 import websocket이 이 패키지다.
/history와 /view로 파일 받기
끝을 확인했으면 /history/{prompt_id}를 연다. execution.py는 작업이 끝날 때 그 작업을 기록에 넣으면서 prompt · outputs · status 세 칸을 붙인다. status에는 status_str(success 또는 error) · completed · messages가 들어 있어서, 그림이 없을 때 실패였는지 바로 가를 수 있다. outputs는 출력 노드 번호별로 묶여 있고, Save Image 노드라면 그 안의 images 목록에 그림마다 filename · subfolder · type이 있다. Save Image의 type은 output, Preview Image의 type은 temp다.
이 세 값을 그대로 /view?filename=…&subfolder=…&type=…에 붙이면 그림 파일의 바이트가 온다. /view는 파일 이름에 ..이 있거나 지정한 폴더 밖을 가리키면 거절하므로, 기록에서 받은 값을 손대지 않고 넘기는 편이 안전하다.
웹소켓 없이 기다리는 길도 있다. execution.py의 기록은 작업이 끝날 때 생기고, 기록에 없는 prompt_id를 물으면 빈 객체 {}를 돌려준다. 그러니 1초쯤 간격으로 /history/{prompt_id}를 열어 보다가 응답에 자기 prompt_id가 생기면 끝난 것으로 보면 된다. 진행률은 볼 수 없지만 표준 라이브러리만으로 되고, 아래 여러 장 스크립트가 이 길을 쓴다.
셋째 방법은 디스크에 아예 저장하지 않는다. 워크플로의 Save Image 대신 SaveImageWebsocket 노드를 두면, 그림이 웹소켓 바이너리 메시지로 바로 오고 앞 8바이트를 뺀 나머지가 그림 데이터다. 결과를 파일로 남길 필요가 없는 프로그램에 맞는다.
여러 장 자동 생성 스크립트 — 시드와 프롬프트를 바꿔 돌리기
이제 세 걸음을 반복문으로 묶는다. 아래 스크립트는 화면에서 내보낸 workflow_api.json을 읽어, 프롬프트 2개 × 시드 3개 = 6번 요청을 차례로 보내고 결과를 api_out 폴더에 받는다. 웹소켓 패키지 없이 파이썬 표준 라이브러리만 쓴다.
import json, time, uuid, pathlib, urllib.request, urllib.parse, urllib.error
SERVER = "127.0.0.1:8188"
CLIENT_ID = str(uuid.uuid4())
OUT = pathlib.Path("api_out")
def queue(workflow):
body = json.dumps({"prompt": workflow, "client_id": CLIENT_ID}).encode("utf-8")
req = urllib.request.Request(f"http://{SERVER}/prompt", data=body,
headers={"Content-Type": "application/json"})
try:
with urllib.request.urlopen(req) as r:
return json.loads(r.read())["prompt_id"]
except urllib.error.HTTPError as e:
err = json.loads(e.read())
print(err["error"]["message"], err["error"]["details"])
print(json.dumps(err["node_errors"], ensure_ascii=False, indent=1))
raise SystemExit(1)
def wait(prompt_id, every=1.0):
while True:
with urllib.request.urlopen(f"http://{SERVER}/history/{prompt_id}") as r:
h = json.loads(r.read())
if prompt_id in h:
return h[prompt_id]
time.sleep(every)
def download(img):
q = urllib.parse.urlencode({"filename": img["filename"],
"subfolder": img["subfolder"], "type": img["type"]})
with urllib.request.urlopen(f"http://{SERVER}/view?{q}") as r:
return r.read()
def node_id(workflow, class_type, title=None):
for nid, node in workflow.items():
if node["class_type"] == class_type and (
title is None or node.get("_meta", {}).get("title") == title):
return nid
raise KeyError(class_type)
base = json.loads(pathlib.Path("workflow_api.json").read_text(encoding="utf-8"))
ks = node_id(base, "KSampler")
pos = base[ks]["inputs"]["positive"][0]
OUT.mkdir(exist_ok=True)
prompts = ["a red bicycle by the sea", "a glass teapot on a wooden table"]
seeds = [101, 202, 303]
for i, text in enumerate(prompts):
for seed in seeds:
wf = json.loads(json.dumps(base))
wf[pos]["inputs"]["text"] = text
wf[ks]["inputs"]["seed"] = seed
pid = queue(wf)
h = wait(pid)
st = h.get("status") or {}
if st.get("status_str") != "success":
print("failed", pid, st.get("messages", [])[-1:])
continue
for out in h["outputs"].values():
for img in out.get("images", []):
name = OUT / f"p{i}_s{seed}_{img['filename']}"
name.write_bytes(download(img))
print("saved", name)몇 군데는 일부러 이렇게 짰다. 노드 번호를 "3"이나 "6"으로 박아 두지 않고 node_id()로 찾는다. 워크플로를 고쳐 다시 내보내면 번호가 바뀔 수 있어서다. 긍정 프롬프트 노드는 KSampler의 positive 입력이 가리키는 노드 번호를 따라가 찾는다. 연결이 ["노드 번호", 출력 순번] 모양이라 [0]이 곧 노드 번호다. 같은 종류 노드가 둘 이상이면 화면에서 노드 제목을 바꿔 두고 title로 고른다. 내보낸 JSON의 _meta.title에 그 제목이 들어간다.
돌리기 전에 알아 둘 것
- 시드는 스크립트가 바꿔야 한다. 화면의 control after generate는 실행 버튼을 누를 때 화면이 시드 칸 값을 바꿔 주는 기능이다. KSampler의 seed 입력에 이 표시가 붙어 있을 뿐, 서버는 받은 JSON을 위젯 값까지 그대로 실행한다. 같은 JSON을 두 번 보내면 README 설명대로 첫 번째만 실행되고, 두 번째는 새 그림 없이 끝난다. 그래서 위 스크립트는 요청마다 seed를 넣는다. seed 칸은 0부터 0xffffffffffffffff까지 받는다. 마음에 든 한 장의 시드를 묶고 다른 칸만 바꿔 보는 순서는 시드와 CFG 편과 같다.
- 바뀐 노드만 다시 돈다. README는 그래프의 바뀐 부분과 거기에 기대는 부분만 다시 실행된다고 적었다. 시드만 바꾸면 체크포인트 불러오기와 프롬프트 인코딩은 앞 작업의 결과를 다시 쓰고, KSampler 뒤만 새로 돈다. 대기열이 한 작업씩 꺼내 실행하는 구조는 ComfyUI 여러 장 뽑기 편에 정리했다.
- 기다리지 않고 한꺼번에 넣어도 된다. /prompt는 넣는 즉시 대답하므로 6번을 먼저 다 보내고 prompt_id 목록을 들고 하나씩 기다려도 된다. 서버가 한 번에 한 작업씩 실행하는 것은 같다. 급한 것 하나를 앞에 세우려면 요청에
"front": true를 붙인다. server.py는 이때 번호를 음수로 바꿔 대기열 앞쪽에 세운다. - 멈추는 경로도 알아 둔다. POST
/queue에{"clear": true}를 보내면 기다리는 작업이 비워지고,{"delete": [prompt_id]}로 하나만 뺄 수 있다. 실행 중인 작업은 POST/interrupt에 prompt_id를 실어 멈춘다. 반복문을 Ctrl+C로 끊어도 이미 대기열에 들어간 작업은 서버에서 계속 돈다는 점을 기억해 둔다. - 결과 파일은 서버 쪽 output 폴더에도 남는다. Save Image가 이미 디스크에 저장한 그림을 /view로 한 벌 더 받아 오는 구조다. output 폴더를 따로 정하려면 ComfyUI를 켤 때
--output-directory를 준다.
처음에는 프롬프트 1개 × 시드 1개로 줄여 한 번 돌려 본다. 검증 오류가 나면 스크립트가 node_errors를 그대로 찍고 멈추므로, 노드 번호와 칸 이름을 보고 JSON을 고친 뒤 개수를 늘린다.
자주 묻는 것
ComfyUI API를 쓰려면 따로 설치하거나 켜야 하나요?
따로 켤 것은 없습니다. ComfyUI를 실행하면 http://127.0.0.1:8188에서 HTTP 서버가 자동으로 뜨고, 화면도 이 서버에 요청을 보냅니다. 파이썬 쪽은 표준 라이브러리만으로 /prompt · /history · /view를 부를 수 있고, 웹소켓으로 진행을 받으려면 websocket-client 패키지를 설치합니다.
화면에서 저장한 워크플로 JSON을 그대로 보내면 안 되나요?
/prompt에는 API 형식 JSON을 보냅니다. 화면의 File → Save로 저장한 파일은 위치 · 색 · 그룹이 담긴 저장 형식이라 구조가 다릅니다. 화면에서 그 파일을 연 다음 File → Export Workflow (API)로 다시 내보내면 노드 번호를 키로 쓰는 API 형식 파일이 됩니다.
같은 JSON을 여러 번 보냈는데 그림이 한 장만 생겼어요.
같은 그래프를 두 번 넣으면 첫 번째만 실행되기 때문입니다. 화면의 control after generate는 실행 버튼을 누를 때 화면이 시드를 바꿔 주는 기능이라, API로 보낸 JSON에는 적용되지 않습니다. 요청마다 KSampler의 seed 값을 스크립트에서 바꿔 넣습니다.
다른 PC나 휴대폰에서 이 API를 부를 수 있나요?
기본값으로는 안 됩니다. --listen 기본값이 127.0.0.1이라 같은 PC 안의 프로그램만 닿습니다. --listen을 주면 다른 주소로도 열리지만, 그러면 같은 네트워크의 다른 기기도 이 ComfyUI에 작업을 넣고 결과를 받아 갈 수 있게 됩니다. 이 글은 같은 PC 안에서 부르는 경우만 다룹니다.
공식 파이썬 SDK도 있나요?
있습니다. 공식 문서는 pip install comfy-sdk로 설치하는 Comfy SDK를 안내합니다. 다만 자기 ComfyUI에 쓰려면 comfy-api-proxy를 함께 띄우고 http://127.0.0.1:8189를 주소로 쓰는 구조라, 이 글처럼 본체의 /prompt를 바로 부르는 방식과는 다릅니다.
정리 — ComfyUI API를 파이썬으로 부르는 세 걸음
ComfyUI API는 따로 켜는 기능이 아니라, 화면이 이미 쓰고 있는 127.0.0.1:8188의 서버 그 자체다. 스크립트가 할 일은 세 걸음이다. 화면에서 Export Workflow (API)로 받은 JSON의 칸 값을 바꿔 /prompt로 보내고, 웹소켓의 executing 메시지나 /history로 끝을 확인하고, 기록의 filename · subfolder · type으로 /view에서 그림을 받는다.
여러 장을 뽑을 때 기억할 것은 두 가지다. 시드는 화면이 아니라 스크립트가 바꿔야 하고, 반복문을 끊어도 대기열에 들어간 작업은 /queue나 /interrupt로 따로 멈춰야 한다. 그리고 서버는 기본값인 127.0.0.1에 그대로 둔다. ComfyUI는 자주 갱신되므로, 판을 올린 뒤에는 저장소의 script_examples와 공식 문서의 서버 경로 표를 한 번 더 열어 본다.
참고 자료
- ComfyUI 공식 문서. Server Overview — ComfyUI를 켜면 http://127.0.0.1:8188에 HTTP 서버가 뜬다는 문장, --disable-auto-launch · --port · --listen 예, 요청을 넣을 때 위젯 값까지 워크플로 전체를 보내고 그 뒤 화면에서 바꾼 것은 서버가 받지 않는다는 안내(2026-10-28 확인). docs.comfy.org
- ComfyUI 공식 문서. API Examples — 세 방법(HTTP만 · WebSocket + History · SaveImageWebsocket), WebSocket + History를 «Recommended»로 표시, websocket-client 패키지 설치 안내, SaveImageWebsocket은 디스크에 저장하지 않고 웹소켓 바이너리로 보냄 · 앞 8바이트는 형식 정보(2026-10-28 확인). docs.comfy.org
- ComfyUI 공식 문서. ComfyUI Server Routes — /prompt가 검증 뒤 대기열에 넣고 prompt_id · number 또는 error · node_errors를 돌려준다는 문장, 기본 경로 표(/ws · /prompt · /history · /view · /queue · /interrupt · /free · /object_info · /system_stats)(2026-10-28 확인). docs.comfy.org
- ComfyUI 공식 문서. ComfyUI Server Messages — 웹소켓 메시지 종류(execution_start · execution_cached · executing · executed · progress · execution_success · execution_error · execution_interrupted · status), executing의 node가 None이면 끝(2026-10-28 확인). docs.comfy.org
- ComfyUI 공식 문서. Workflow API Format — 저장 형식과 API 형식 비교표, File → Export Workflow (API), API 형식은 노드 번호를 키로 쓰고 위치 · 색 · 그룹을 뺌, 예시 JSON의 class_type · inputs · _meta.title(2026-10-28 확인). docs.comfy.org
- ComfyUI 공식 문서. Startup Flags — --listen 기본 127.0.0.1 · 값 없이 주면 0.0.0.0,:: · --port 기본 8188(2026-10-28 확인). docs.comfy.org
- ComfyUI GitHub. script_examples — basic_api_example.py · websockets_api_example.py · websockets_api_example_ws_images.py 세 파일, 마지막 바뀐 날 2025-09-03(GitHub API 조회 · 2026-10-28). github.com
- ComfyUI 소스 코드 server.py — POST /prompt의 number · front · prompt_id(UUID 소문자 하이픈 형식만) · client_id 처리와 200 · 400 응답 모양, /ws의 clientId, /history/{prompt_id}, /view의 filename · subfolder · type과 '..' 막기, POST /queue clear · delete, POST /interrupt(2026-10-28 master). github.com
- ComfyUI 소스 코드 execution.py — validate_prompt의 prompt_no_outputs · prompt_outputs_failed_validation · value_not_in_list · missing_node_type, node_errors 모양(errors · dependent_outputs · class_type), task_done이 끝난 작업만 기록에 넣고 status_str(success · error) · completed · messages를 붙이는 부분, 없는 id는 빈 객체, 기록 최대 10000개(2026-10-28 master). github.com
- ComfyUI 소스 코드 comfy/cli_args.py — --listen 기본 127.0.0.1 · 값 없이 0.0.0.0,:: · --port 기본 8188 · --enable-cors-header · --output-directory(2026-10-28 master). github.com
- ComfyUI 소스 코드 nodes.py — KSampler seed 입력(최소 0 · 최대 0xffffffffffffffff · control_after_generate), SaveImage가 ui.images에 filename · subfolder · type(output)을 돌려주는 부분, PreviewImage는 temp(2026-10-28 master). github.com
- ComfyUI README — 같은 그래프를 두 번 넣으면 첫 번째만 실행되고 바뀐 부분만 다시 실행된다는 설명(2026-10-28 master). github.com
- ComfyUI GitHub 릴리스 v0.43.0 — 2026-10-27 공개(GitHub API 조회 · 2026-10-28). github.com
- PyPI. websocket-client — 최신 1.9.2 · Python 3.10 이상 · Apache-2.0(2026-10-28 PyPI JSON 조회). pypi.org
- ComfyUI 공식 문서. Comfy SDKs — pip install comfy-sdk, 자기 ComfyUI에 쓰려면 comfy-api-proxy를 함께 띄우고 http://127.0.0.1:8189를 주소로 씀(2026-10-28 확인). docs.comfy.org