실험적 API: 이 API는 실험적이며 변경될 수 있습니다. 엔드포인트, 요청/응답 형식 및 동작은 사전 통지 없이 수정될 수 있습니다. 일부 엔드포인트는 로컬 ComfyUI와의 호환성을 위해 유지되지만 다른 의미를 가질 수 있습니다(예: 무시된 필드).
이 페이지에서는 일반적인 Comfy Cloud API 작업에 대한 완벽한 예제를 제공합니다.
구독 필요: API를 통해 워크플로우를 실행하려면 활성 Comfy Cloud 구독이 필요합니다. 자세한 내용은 가격 계획을 참조하세요.
모든 예제는 다음과 같은 공통 임포트와 구성을 사용합니다:
export COMFY_CLOUD_API_KEY="your-api-key"
export BASE_URL="https://cloud.comfy.org"
객체 정보
사용 가능한 노드 정의를 가져옵니다. 이는 어떤 노드들이 있는지, 그리고 그 입력과 출력 사양을 이해하는 데 유용합니다.
curl -X GET "$BASE_URL/api/object_info" \
-H "X-API-Key: $COMFY_CLOUD_API_KEY"
입력 업로드
워크플로우에서 사용할 이미지, 마스크 또는 기타 파일을 업로드합니다.
직접 업로드 (멀티파트)
curl -X POST "$BASE_URL/api/upload/image" \
-H "X-API-Key: $COMFY_CLOUD_API_KEY" \
-F "image=@my_image.png" \
-F "type=input" \
-F "overwrite=true"
마스크 업로드
subfolder 매개변수는 API 호환성을 위해 허용되지만 클라우드 스토리지에서는 무시됩니다. 모든 파일은 평탄하고 콘텐츠 주소 기반 네임스페이스에 저장됩니다.
curl -X POST "$BASE_URL/api/upload/mask" \
-H "X-API-Key: $COMFY_CLOUD_API_KEY" \
-F "image=@mask.png" \
-F "type=input" \
-F "subfolder=clipspace" \
-F 'original_ref={"filename":"my_image.png","subfolder":"","type":"input"}'
워크플로우 실행
워크플로우를 제출하여 실행합니다.
동시 제출 지원: 구독 등급에 따라 이전 작업이 완료될 때까지 기다리지 않고 여러 워크플로우를 제출할 수 있습니다. 등급 한도 내에서 작업이 병렬로 실행되며, 초과 작업은 자동으로 대기열에 추가됩니다. 자세한 내용과 동시성 한도는 병렬 실행을 참조하세요.
워크플로우 제출
curl -X POST "$BASE_URL/api/prompt" \
-H "X-API-Key: $COMFY_CLOUD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": '"$(cat workflow_api.json)"'}'
파트너 노드 사용
워크플로우에 파트너 노드 (Flux Pro, Ideogram 등 외부 AI 서비스를 호출하는 노드)가 포함된 경우, 요청 페이로드의 extra_data 필드에 Comfy API 키를 포함해야 합니다.
ComfyUI 프론트엔드는 브라우저에서 워크플로우를 실행할 때 자동으로 API 키를 extra_data에 패키징합니다. 이 섹션은 직접 API를 호출할 때만 관련이 있습니다.
curl -X POST "$BASE_URL/api/prompt" \
-H "X-API-Key: $COMFY_CLOUD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": '"$(cat workflow_api.json)"',
"extra_data": {
"api_key_comfy_org": "your-comfy-api-key"
}
}'
function setWorkflowInput(
workflow: Record<string, any>,
nodeId: string,
inputName: string,
value: any
): Record<string, any> {
if (workflow[nodeId]) {
workflow[nodeId].inputs[inputName] = value;
}
return workflow;
}
// 예제: 시드와 프롬프트 설정
let workflow = JSON.parse(await readFile("workflow_api.json", "utf-8"));
workflow = setWorkflowInput(workflow, "3", "seed", 12345);
workflow = setWorkflowInput(workflow, "6", "text", "아름다운 풍경");
작업 상태 확인
작업 완료 여부를 휠체어로 확인합니다.
작업 상태 값:
API는 다음 상태 값을 반환합니다:
| 상태 | 설명 |
|---|
pending | 작업이 대기 중이며 시작을 기다리고 있습니다 |
in_progress | 작업이 현재 실행 중입니다 |
completed | 작업이 성공적으로 완료되었습니다 |
failed | 작업 중 오류가 발생했습니다 |
cancelled | 사용자가 작업을 취소했습니다 |
# 작업 완료 여부를 풀링합니다
curl -X GET "$BASE_URL/api/job/{prompt_id}/status" \
-H "X-API-Key: $COMFY_CLOUD_API_KEY"
# 응답 예시:
# {"status": "pending"} - 작업이 대기 중입니다
# {"status": "in_progress"} - 작업이 현재 실행 중입니다
# {"status": "completed"} - 작업이 성공적으로 완료되었습니다
# {"status": "failed"} - 작업 중 오류가 발생했습니다
# {"status": "cancelled"} - 작업이 취소되었습니다
실시간 진행 상황을 위한 WebSocket
WebSocket 연결을 통해 실시간 실행 업데이트를 받습니다.
clientId 매개변수는 현재 무시됩니다—사용자당 모든 연결이 동일한 메시지를 받습니다. 앞으로의 호환성을 위해 고유한 clientId를 전달하세요.
async function listenForCompletion(
promptId: string,
timeout: number = 300000
): Promise<Record<string, any>> {
const wsUrl = `wss://cloud.comfy.org/ws?clientId=${crypto.randomUUID()}&token=${API_KEY}`;
const outputs: Record<string, any> = {};
return new Promise((resolve, reject) => {
const ws = new WebSocket(wsUrl);
const timer = setTimeout(() => {
ws.close();
reject(new Error(`작업이 ${timeout / 1000}s 내에 완료되지 않았습니다`));
}, timeout);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
const msgType = data.type;
const msgData = data.data ?? {};
// 우리의 작업으로 필터링
if (msgData.prompt_id !== promptId) return;
if (msgType === "executing") {
const node = msgData.node;
if (node) {
console.log(`실행 중인 노드: ${node}`);
} else {
console.log("실행 완료");
}
} else if (msgType === "progress") {
console.log(`진행률: ${msgData.value}/${msgData.max}`);
} else if (msgType === "executed" && msgData.output) {
outputs[msgData.node] = msgData.output;
} else if (msgType === "execution_success") {
console.log("작업이 성공적으로 완료되었습니다!");
clearTimeout(timer);
ws.close();
resolve(outputs);
} else if (msgType === "execution_error") {
const errorMsg = msgData.exception_message ?? "알 수 없는 오류";
clearTimeout(timer);
ws.close();
reject(new Error(`실행 오류: ${errorMsg}`));
}
};
ws.onerror = (err) => {
clearTimeout(timer);
reject(err);
};
});
}
// 완료를 기다리고 출력값 수집
const outputs = await listenForCompletion(promptId);
WebSocket 메시지 유형
메시지는 특별히 언급되지 않은 한 JSON 텍스트 프레임으로 전송됩니다.
| 유형 | 설명 |
|---|
status | 대기열 상태 업데이트, queue_remaining 카운트 포함 |
notification | 사용자 친화적 상태 메시지 (value 필드에는 “워크플로우 실행 중…”과 같은 텍스트 포함) |
execution_start | 워크플로우 실행 시작 |
executing | 특정 노드가 현재 실행 중 (노드 ID는 node 필드에 있음) |
progress | 노드 내 단계 진행 상황 (value/max는 샘플링 단계) |
progress_state | 노드 메타데이터를 포함한 확장된 진행 상태 (중첩된 nodes 객체) |
executed | 노드가 완료되고 출력물(이미지, 비디오 등 output 필드에 있음) 생성 |
execution_cached | 출력물이 캐시되어 건너뛴 노드 (nodes 배열) |
execution_success | 전체 워크플로우 성공적으로 완료 |
execution_error | 워크플로우 실패 (exception_type, exception_message, traceback 포함) |
execution_interrupted | 사용자가 워크플로우를 취소함 |
바이너리 메시지 (미리보기 이미지)
이미지 생성 중에 ComfyUI는 미리보기 이미지를 포함한 바이너리 WebSocket 프레임을 전송합니다. 이는 원시 바이너리 데이터(JSON 아님)입니다:
| 바이너리 유형 | 값 | 설명 |
|---|
PREVIEW_IMAGE | 1 | 디퓨전 샘플링 중 진행 중인 미리보기 |
TEXT | 3 | 노드에서 출력된 텍스트 (진행률 텍스트) |
PREVIEW_IMAGE_WITH_METADATA | 4 | 노드 컨텍스트 메타데이터를 포함한 미리보기 이미지 |
바이너리 프레임 형식 (모든 정수는 빅엔디안):
| 오프셋 | 크기 | 필드 | 설명 |
|---|
| 0 | 4바이트 | type | 0x00000001 |
| 4 | 4바이트 | image_type | 포맷 코드 (1=JPEG, 2=PNG) |
| 8 | 변수 | image_data | 이미지 바이트 원시 데이터 |
| 오프셋 | 크기 | 필드 | 설명 |
|---|
| 0 | 4바이트 | type | 0x00000003 |
| 4 | 4바이트 | node_id_len | 노드 ID 문자열 길이 |
| 8 | N바이트 | node_id | UTF-8 인코딩된 노드 ID |
| 8+N | 변수 | text | UTF-8 인코딩된 진행률 텍스트 |
| 오프셋 | 크기 | 필드 | 설명 |
|---|
| 0 | 4바이트 | type | 0x00000004 |
| 4 | 4바이트 | metadata_len | 메타데이터 JSON 길이 |
| 8 | N바이트 | metadata | UTF-8 JSON (아래 참조) |
| 8+N | 변수 | image_data | JPEG/PNG 원시 바이트 |
메타데이터 JSON 구조:{
"node_id": "3",
"display_node_id": "3",
"real_node_id": "3",
"prompt_id": "abc-123",
"parent_node_id": null
}
출력 다운로드
작업 완료 후 생성된 파일을 가져옵니다.
# 단일 출력 파일 다운로드하기 (302 리디렉션은 -L 옵션으로 따라가기)
curl -L "$BASE_URL/api/view?filename=output.png&subfolder=&type=output" \
-H "X-API-Key: $COMFY_CLOUD_API_KEY" \
-o output.png
완전한 엔드투엔드 예제
모든 것을 하나로 묶은 전체 예제는 다음과 같습니다:
const BASE_URL = "https://cloud.comfy.org";
const API_KEY = process.env.COMFY_CLOUD_API_KEY!;
function getHeaders(): HeadersInit {
return { "X-API-Key": API_KEY, "Content-Type": "application/json" };
}
async function submitWorkflow(workflow: Record<string, any>): Promise<string> {
const response = await fetch(`${BASE_URL}/api/prompt`, {
method: "POST",
headers: getHeaders(),
body: JSON.stringify({ prompt: workflow }),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return (await response.json()).prompt_id;
}
async function waitForCompletion(
promptId: string,
timeout: number = 300000
): Promise<Record<string, any>> {
const wsUrl = `wss://cloud.comfy.org/ws?clientId=${crypto.randomUUID()}&token=${API_KEY}`;
const outputs: Record<string, any> = {};
return new Promise((resolve, reject) => {
const ws = new WebSocket(wsUrl);
const timer = setTimeout(() => {
ws.close();
reject(new Error("작업 시간 초과"));
}, timeout);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.data?.prompt_id !== promptId) return;
const msgType = data.type;
const msgData = data.data ?? {};
if (msgType === "progress") {
console.log(`진행률: ${msgData.value}/${msgData.max}`);
} else if (msgType === "executed" && msgData.output) {
outputs[msgData.node] = msgData.output;
} else if (msgType === "execution_success") {
clearTimeout(timer);
ws.close();
resolve(outputs);
} else if (msgType === "execution_error") {
clearTimeout(timer);
ws.close();
reject(new Error(msgData.exception_message ?? "알 수 없는 오류"));
}
};
ws.onerror = (err) => {
clearTimeout(timer);
reject(err);
};
});
}
async function downloadOutputs(
outputs: Record<string, any>,
outputDir: string
): Promise<void> {
for (const nodeOutputs of Object.values(outputs)) {
for (const key of ["images", "video", "audio"]) {
for (const fileInfo of (nodeOutputs as any)[key] ?? []) {
const params = new URLSearchParams({
filename: fileInfo.filename,
subfolder: fileInfo.subfolder ?? "",
type: fileInfo.type ?? "output",
});
// 리디렉션 URL 가져오기 (인증 정보를 저장소로 보내지 않도록)
const response = await fetch(`${BASE_URL}/api/view?${params}`, {
headers: { "X-API-Key": API_KEY },
redirect: "manual",
});
if (response.status !== 302) throw new Error(`HTTP ${response.status}`);
const signedUrl = response.headers.get("location")!;
// 서명된 URL에서 인증 헤더 없이 가져오기
const fileResponse = await fetch(signedUrl);
if (!fileResponse.ok) throw new Error(`HTTP ${fileResponse.status}`);
const path = `${outputDir}/${fileInfo.filename}`;
await writeFile(path, Buffer.from(await fileResponse.arrayBuffer()));
console.log(`다운로드됨: ${path}`);
}
}
}
}
async function main() {
// 1. 워크플로우 로드
const workflow = JSON.parse(await readFile("workflow_api.json", "utf-8"));
// 2. 워크플로우 파라미터 수정
workflow["3"].inputs.seed = 42;
workflow["6"].inputs.text = "산 위의 아름다운 일몰";
// 3. 워크플로우 제출
const promptId = await submitWorkflow(workflow);
console.log(`작업 제출됨: ${promptId}`);
// 4. 진행 상황과 함께 완료 대기
const outputs = await waitForCompletion(promptId);
console.log(`작업 완료! ${Object.keys(outputs).length}개의 출력 노드 발견`);
// 5. 출력 다운로드
await downloadOutputs(outputs, "./outputs");
console.log("완료!");
}
main();
대기열 관리
대기열 상태 가져오기
curl -X GET "$BASE_URL/api/queue" \
-H "X-API-Key: $COMFY_CLOUD_API_KEY"
작업 취소
curl -X POST "$BASE_URL/api/queue" \
-H "X-API-Key: $COMFY_CLOUD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"delete": ["PROMPT_ID_HERE"]}'
현재 실행 중단
curl -X POST "$BASE_URL/api/interrupt" \
-H "X-API-Key: $COMFY_CLOUD_API_KEY"
오류 처리
HTTP 오류
REST API 엔드포인트는 표준 HTTP 상태 코드를 반환합니다:
| 상태 | 설명 |
|---|
400 | 잘못된 요청 (잘못된 워크플로우, 필드 누락) |
401 | 인증되지 않음 (잘못된 또는 누락된 API 키) |
402 | 크레딧 부족 |
429 | 구독 비활성 |
500 | 내부 서버 오류 |
실행 오류
워크플로우 실행 중 오류는 execution_error WebSocket 메시지를 통해 전달됩니다. exception_type 필드는 오류 범주를 식별합니다:
| 예외 유형 | 설명 |
|---|
ValidationError | 유효하지 않은 워크플로우 또는 입력 |
ModelDownloadError | 필요한 모델이 없거나 다운로드 실패 |
ImageDownloadError | URL에서 입력 이미지 다운로드 실패 |
OOMError | GPU 메모리 부족 |
InsufficientFundsError | 계정 잔액 부족 (파트너 노드용) |
InactiveSubscriptionError | 구독 비활성 |