Skip to main content
Experimental API: This API is experimental and subject to change. Endpoints, request/response formats, and behavior may be modified without notice.

Cloud API Reference

This page provides complete examples for common Comfy Cloud API operations.
Subscription Required: Running workflows via the API requires an active Comfy Cloud subscription. See pricing plans for details.

Setup

All examples use these common imports and configuration:
export COMFY_CLOUD_API_KEY="your-api-key"
export BASE_URL="https://cloud.comfy.org"

Object Info

Retrieve available node definitions. This is useful for understanding what nodes are available and their input/output specifications.
curl -X GET "$BASE_URL/api/object_info" \
  -H "X-API-Key: $COMFY_CLOUD_API_KEY"

Uploading Inputs

Upload images, masks, or other files for use in workflows.

Direct Upload (Multipart)

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"

Upload Mask

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"}'

Reference Well-Known Assets (Skip Upload)

If you know a file already exists in cloud storage (e.g., a popular sample image shared by Comfy), you can create an asset reference without uploading any bytes. First check if the hash exists, then create your own reference to it.
# 1. Check if the hash exists (unauthenticated)
# Hash format: blake3:<64 hex chars>
curl -I "$BASE_URL/api/assets/hash/blake3:a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd"
# Returns 200 if exists, 404 if not

# 2. Create your own asset reference pointing to that hash
curl -X POST "$BASE_URL/api/assets/from-hash" \
  -H "X-API-Key: $COMFY_CLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "hash": "blake3:a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd",
    "tags": ["input"],
    "name": "sample_portrait.png"
  }'

Running Workflows

Submit a workflow for execution.

Submit Workflow

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)"'}'

Modify Workflow Inputs

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;
}

// Example: Set seed and prompt
let workflow = JSON.parse(await readFile("workflow_api.json", "utf-8"));
workflow = setWorkflowInput(workflow, "3", "seed", 12345);
workflow = setWorkflowInput(workflow, "6", "text", "a beautiful landscape");

Checking Job Status

Poll for job completion status.
# Get job status
curl -X GET "$BASE_URL/api/job/{prompt_id}/status" \
  -H "X-API-Key: $COMFY_CLOUD_API_KEY"

WebSocket for Real-Time Progress

Connect to the WebSocket for real-time execution updates.
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(`Job did not complete within ${timeout / 1000}s`));
    }, timeout);

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      const msgType = data.type;
      const msgData = data.data ?? {};

      // Filter to our job
      if (msgData.prompt_id !== promptId) return;

      if (msgType === "executing") {
        const node = msgData.node;
        if (node) {
          console.log(`Executing node: ${node}`);
        } else {
          console.log("Execution complete");
        }
      } else if (msgType === "progress") {
        console.log(`Progress: ${msgData.value}/${msgData.max}`);
      } else if (msgType === "executed" && msgData.output) {
        outputs[msgData.node] = msgData.output;
      } else if (msgType === "execution_success") {
        console.log("Job completed successfully!");
        clearTimeout(timer);
        ws.close();
        resolve(outputs);
      } else if (msgType === "execution_error") {
        const errorMsg = msgData.exception_message ?? "Unknown error";
        const nodeType = msgData.node_type ?? "";
        clearTimeout(timer);
        ws.close();
        reject(new Error(`Execution error in ${nodeType}: ${errorMsg}`));
      }
    };

    ws.onerror = (err) => {
      clearTimeout(timer);
      reject(err);
    };
  });
}

// Usage
const promptId = await submitWorkflow(workflow);
const outputs = await listenForCompletion(promptId);

WebSocket Message Types

Messages are sent as JSON text frames unless otherwise noted.
TypeDescription
statusQueue status update with queue_remaining count
execution_startWorkflow execution has started
executingA specific node is now executing (node ID in node field)
progressStep progress within a node (value/max for sampling steps)
progress_stateExtended progress state with node metadata (nested nodes object)
executedNode completed with outputs (images, video, etc. in output field)
execution_cachedNodes skipped because outputs are cached (nodes array)
execution_successEntire workflow completed successfully
execution_errorWorkflow failed (includes exception_type, exception_message, traceback)
execution_interruptedWorkflow was cancelled by user

Binary Messages (Preview Images)

During image generation, ComfyUI sends binary WebSocket frames containing preview images. These are raw binary data (not JSON):
Binary TypeValueDescription
PREVIEW_IMAGE1In-progress preview during diffusion sampling
TEXT3Text output from nodes (progress text)
PREVIEW_IMAGE_WITH_METADATA4Preview image with node context metadata
Binary frame formats (all integers are big-endian):
OffsetSizeFieldDescription
04 bytestype0x00000001
44 bytesimage_typeFormat code (1=JPEG, 2=PNG)
8variableimage_dataRaw image bytes
See the OpenAPI Specification for complete schema definitions of each JSON message type.

Downloading Outputs

Retrieve generated files after job completion.
# Download a single output file
curl -X GET "$BASE_URL/api/view?filename=output.png&subfolder=&type=output" \
  -H "X-API-Key: $COMFY_CLOUD_API_KEY" \
  -o output.png

Complete End-to-End Example

Here’s a full example that ties everything together:
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("Job timed out"));
    }, 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(`Progress: ${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 ?? "Unknown error"));
      }
    };

    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",
        });
        const response = await fetch(`${BASE_URL}/api/view?${params}`, {
          headers: getHeaders(),
        });
        if (!response.ok) throw new Error(`HTTP ${response.status}`);

        const path = `${outputDir}/${fileInfo.filename}`;
        await writeFile(path, Buffer.from(await response.arrayBuffer()));
        console.log(`Downloaded: ${path}`);
      }
    }
  }
}

async function main() {
  // 1. Load workflow
  const workflow = JSON.parse(await readFile("workflow_api.json", "utf-8"));

  // 2. Modify workflow parameters
  workflow["3"].inputs.seed = 42;
  workflow["6"].inputs.text = "a beautiful sunset over mountains";

  // 3. Submit workflow
  const promptId = await submitWorkflow(workflow);
  console.log(`Job submitted: ${promptId}`);

  // 4. Wait for completion with progress
  const outputs = await waitForCompletion(promptId);
  console.log(`Job completed! Found ${Object.keys(outputs).length} output nodes`);

  // 5. Download outputs
  await downloadOutputs(outputs, "./outputs");
  console.log("Done!");
}

main();

Queue Management

Get Queue Status

curl -X GET "$BASE_URL/api/queue" \
  -H "X-API-Key: $COMFY_CLOUD_API_KEY"

Cancel a Job

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"]}'

Interrupt Current Execution

curl -X POST "$BASE_URL/api/interrupt" \
  -H "X-API-Key: $COMFY_CLOUD_API_KEY"

Error Handling

The API returns structured errors via the execution_error WebSocket message or HTTP error responses. The exception_type field identifies the error category:
Exception TypeDescription
ValidationErrorInvalid workflow or inputs
ModelDownloadErrorRequired model not available or failed to download
ImageDownloadErrorFailed to download input image from URL
OOMErrorOut of GPU memory
PanicErrorUnexpected server crash during execution
ServiceErrorInternal service error
WebSocketErrorWebSocket connection or communication error
DispatcherErrorJob dispatch or routing error
InsufficientFundsErrorAccount balance too low
InactiveSubscriptionErrorSubscription not active
async function handleApiError(response: Response): Promise<never> {
  if (response.status === 402) {
    throw new Error("Insufficient credits. Please add funds to your account.");
  } else if (response.status === 429) {
    throw new Error("Rate limited or subscription inactive.");
  } else if (response.status >= 400) {
    try {
      const error = await response.json();
      throw new Error(`API error: ${error.message ?? response.statusText}`);
    } catch {
      throw new Error(`API error: ${response.statusText}`);
    }
  }
  throw new Error("Unknown error");
}

// Usage
const response = await fetch(`${BASE_URL}/api/prompt`, {
  method: "POST",
  headers: getHeaders(),
  body: JSON.stringify({ prompt: workflow }),
});
if (!response.ok) {
  await handleApiError(response);
}