Files
arcum42-ComfyUI_SageUtils/ref_docs/upstream_docs/cloud/api-reference.md
T

931 lines
27 KiB
Markdown

---
title: "Cloud API Reference"
description: "Complete API reference with code examples for Comfy Cloud"
---
import PollJobCompletion from '/snippets/cloud/poll-job-completion.mdx'
import WebSocketProgress from '/snippets/cloud/websocket-progress.mdx'
import DownloadOutputs from '/snippets/cloud/download-outputs.mdx'
<Warning>
**Experimental API:** This API is experimental and subject to change. Endpoints, request/response formats, and behavior may be modified without notice. Some endpoints are maintained for compatibility with local ComfyUI but may have different semantics (e.g., ignored fields).
</Warning>
This page provides complete examples for common Comfy Cloud API operations.
<Note>
**Subscription Required:** Running workflows via the API requires an active Comfy Cloud subscription. See [pricing plans](https://www.comfy.org/cloud/pricing?utm_source=docs&utm_campaign=cloud-api) for details.
</Note>
## Setup
All examples use these common imports and configuration:
<CodeGroup>
```bash curl
export COMFY_CLOUD_API_KEY="your-api-key"
export BASE_URL="https://cloud.comfy.org"
```
```typescript TypeScript
import { readFile, writeFile } from "fs/promises";
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",
};
}
```
```python Python
import os
import requests
import json
import time
import asyncio
import aiohttp
BASE_URL = "https://cloud.comfy.org"
API_KEY = os.environ["COMFY_CLOUD_API_KEY"]
def get_headers():
return {
"X-API-Key": API_KEY,
"Content-Type": "application/json"
}
```
</CodeGroup>
---
## Object Info
Retrieve available node definitions. This is useful for understanding what nodes are available and their input/output specifications.
<CodeGroup>
```bash curl
curl -X GET "$BASE_URL/api/object_info" \
-H "X-API-Key: $COMFY_CLOUD_API_KEY"
```
```typescript TypeScript
async function getObjectInfo(): Promise<Record<string, any>> {
const response = await fetch(`${BASE_URL}/api/object_info`, {
headers: getHeaders(),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
const objectInfo = await getObjectInfo();
console.log(`Available nodes: ${Object.keys(objectInfo).length}`);
const ksampler = objectInfo["KSampler"] ?? {};
console.log(`KSampler inputs: ${Object.keys(ksampler.input?.required ?? {})}`);
```
```python Python
def get_object_info():
"""Fetch all available node definitions from cloud."""
response = requests.get(
f"{BASE_URL}/api/object_info",
headers=get_headers()
)
response.raise_for_status()
return response.json()
# Get all nodes
object_info = get_object_info()
print(f"Available nodes: {len(object_info)}")
# Get a specific node's definition
ksampler = object_info.get("KSampler", {})
inputs = list(ksampler.get('input', {}).get('required', {}).keys())
print(f"KSampler inputs: {inputs}")
```
</CodeGroup>
---
## Uploading Inputs
Upload images, masks, or other files for use in workflows.
### Direct Upload (Multipart)
<CodeGroup>
```bash curl
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"
```
```typescript TypeScript
async function uploadInput(
filePath: string,
inputType: string = "input"
): Promise<{ name: string; subfolder: string }> {
const file = await readFile(filePath);
const formData = new FormData();
formData.append("image", new Blob([file]), filePath.split("/").pop());
formData.append("type", inputType);
formData.append("overwrite", "true");
const response = await fetch(`${BASE_URL}/api/upload/image`, {
method: "POST",
headers: { "X-API-Key": API_KEY },
body: formData,
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
const result = await uploadInput("my_image.png");
console.log(`Uploaded: ${result.name} to ${result.subfolder}`);
```
```python Python
def upload_input(file_path: str, input_type: str = "input") -> dict:
"""Upload a file directly to cloud.
Args:
file_path: Path to the file to upload
input_type: "input" for images, "temp" for temporary files
Returns:
Upload response with filename and subfolder
"""
with open(file_path, "rb") as f:
files = {"image": f}
data = {"type": input_type, "overwrite": "true"}
response = requests.post(
f"{BASE_URL}/api/upload/image",
headers={"X-API-Key": API_KEY}, # No Content-Type for multipart
files=files,
data=data
)
response.raise_for_status()
return response.json()
# Upload an image
result = upload_input("my_image.png")
print(f"Uploaded: {result['name']} to {result['subfolder']}")
```
</CodeGroup>
### Upload Mask
<Note>
The `subfolder` parameter is accepted for API compatibility but ignored in cloud storage. All files are stored in a flat, content-addressed namespace.
</Note>
<CodeGroup>
```bash curl
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"}'
```
```typescript TypeScript
async function uploadMask(
filePath: string,
originalRef: { filename: string; subfolder: string; type: string }
): Promise<{ name: string; subfolder: string }> {
const file = await readFile(filePath);
const formData = new FormData();
formData.append("image", new Blob([file]), filePath.split("/").pop());
formData.append("type", "input");
formData.append("subfolder", "clipspace");
formData.append("original_ref", JSON.stringify(originalRef));
const response = await fetch(`${BASE_URL}/api/upload/mask`, {
method: "POST",
headers: { "X-API-Key": API_KEY },
body: formData,
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
const maskResult = await uploadMask("mask.png", {
filename: "my_image.png",
subfolder: "",
type: "input",
});
console.log(`Uploaded mask: ${maskResult.name}`);
```
```python Python
def upload_mask(file_path: str, original_ref: dict) -> dict:
"""Upload a mask associated with an original image.
Args:
file_path: Path to the mask file
original_ref: Reference to the original image {"filename": "...", "subfolder": "...", "type": "..."}
"""
with open(file_path, "rb") as f:
files = {"image": f}
data = {
"type": "input",
"subfolder": "clipspace",
"original_ref": json.dumps(original_ref)
}
response = requests.post(
f"{BASE_URL}/api/upload/mask",
headers={"X-API-Key": API_KEY},
files=files,
data=data
)
response.raise_for_status()
return response.json()
```
</CodeGroup>
---
## Running Workflows
Submit a workflow for execution.
<Info>
**Concurrent submissions supported:** Depending on your subscription tier, you can submit multiple workflows without waiting for previous jobs to complete. Jobs run in parallel up to your tier's limit — additional jobs queue automatically. See [Parallel Execution](/development/cloud/overview#parallel-execution-concurrent-jobs) for details and concurrency limits.
</Info>
### Submit Workflow
<CodeGroup>
```bash curl
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)"'}'
```
```typescript TypeScript
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}`);
const result = await response.json();
if (result.error) {
throw new Error(`Workflow error: ${result.error}`);
}
return result.prompt_id;
}
const workflow = JSON.parse(await readFile("workflow_api.json", "utf-8"));
const promptId = await submitWorkflow(workflow);
console.log(`Submitted job: ${promptId}`);
```
```python Python
def submit_workflow(workflow: dict) -> str:
"""Submit a workflow and return the prompt_id (job ID).
Args:
workflow: ComfyUI workflow in API format
Returns:
prompt_id for tracking the job
"""
response = requests.post(
f"{BASE_URL}/api/prompt",
headers=get_headers(),
json={"prompt": workflow}
)
response.raise_for_status()
result = response.json()
if "error" in result:
raise ValueError(f"Workflow error: {result['error']}")
return result["prompt_id"]
# Load and submit a workflow
with open("workflow_api.json") as f:
workflow = json.load(f)
prompt_id = submit_workflow(workflow)
print(f"Submitted job: {prompt_id}")
```
</CodeGroup>
### Using Partner Nodes
If your workflow contains [Partner Nodes](/tutorials/api-nodes/overview) (nodes that call external AI services like Flux Pro, Ideogram, etc.), you must include your Comfy API key in the `extra_data` field of the request payload.
<Note>
The ComfyUI frontend automatically packages your API key into `extra_data` when running workflows in the browser. This section is only relevant when calling the API directly.
</Note>
<CodeGroup>
```bash curl
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"
}
}'
```
```typescript TypeScript
async function submitWorkflowWithPartnerNodes(
workflow: Record<string, any>,
apiKey: string
): Promise<string> {
const response = await fetch(`${BASE_URL}/api/prompt`, {
method: "POST",
headers: getHeaders(),
body: JSON.stringify({
prompt: workflow,
extra_data: {
api_key_comfy_org: apiKey,
},
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
return result.prompt_id;
}
// Use when workflow contains Partner Nodes (e.g., Flux Pro, Ideogram, etc.)
const promptId = await submitWorkflowWithPartnerNodes(workflow, API_KEY);
```
```python Python
def submit_workflow_with_partner_nodes(workflow: dict, api_key: str) -> str:
"""Submit a workflow that uses Partner Nodes.
Args:
workflow: ComfyUI workflow in API format
api_key: Your API key from platform.comfy.org
Returns:
prompt_id for tracking the job
"""
response = requests.post(
f"{BASE_URL}/api/prompt",
headers=get_headers(),
json={
"prompt": workflow,
"extra_data": {
"api_key_comfy_org": api_key
}
}
)
response.raise_for_status()
return response.json()["prompt_id"]
# Use when workflow contains Partner Nodes
prompt_id = submit_workflow_with_partner_nodes(workflow, API_KEY)
```
</CodeGroup>
<Info>
Generate your API key at [platform.comfy.org](https://platform.comfy.org/login). This is the same key used for Cloud API authentication (`X-API-Key` header).
</Info>
### Modify Workflow Inputs
<CodeGroup>
```typescript TypeScript
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");
```
```python Python
def set_workflow_input(workflow: dict, node_id: str, input_name: str, value) -> dict:
"""Modify a workflow input value.
Args:
workflow: The workflow dict
node_id: ID of the node to modify
input_name: Name of the input field
value: New value
Returns:
Modified workflow
"""
if node_id in workflow:
workflow[node_id]["inputs"][input_name] = value
return workflow
# Example: Set seed and prompt
workflow = set_workflow_input(workflow, "3", "seed", 12345)
workflow = set_workflow_input(workflow, "6", "text", "a beautiful landscape")
```
</CodeGroup>
---
## Checking Job Status
Poll for job completion.
<PollJobCompletion />
---
## WebSocket for Real-Time Progress
Connect to the WebSocket for real-time execution updates.
<Note>
The `clientId` parameter is currently ignored—all connections for a user receive the same messages. Pass a unique `clientId` for forward compatibility.
</Note>
<WebSocketProgress />
### WebSocket Message Types
Messages are sent as JSON text frames unless otherwise noted.
| Type | Description |
|------|-------------|
| `status` | Queue status update with `queue_remaining` count |
| `notification` | User-friendly status message (`value` field contains text like "Executing workflow...") |
| `execution_start` | Workflow execution has started |
| `executing` | A specific node is now executing (node ID in `node` field) |
| `progress` | Step progress within a node (`value`/`max` for sampling steps) |
| `progress_state` | Extended progress state with node metadata (nested `nodes` object) |
| `executed` | Node completed with outputs (images, video, etc. in `output` field) |
| `execution_cached` | Nodes skipped because outputs are cached (`nodes` array) |
| `execution_success` | Entire workflow completed successfully |
| `execution_error` | Workflow failed (includes `exception_type`, `exception_message`, `traceback`) |
| `execution_interrupted` | Workflow 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 Type | Value | Description |
|-------------|-------|-------------|
| `PREVIEW_IMAGE` | `1` | In-progress preview during diffusion sampling |
| `TEXT` | `3` | Text output from nodes (progress text) |
| `PREVIEW_IMAGE_WITH_METADATA` | `4` | Preview image with node context metadata |
**Binary frame formats** (all integers are big-endian):
<Tabs>
<Tab title="PREVIEW_IMAGE (1)">
| Offset | Size | Field | Description |
|--------|------|-------|-------------|
| 0 | 4 bytes | `type` | `0x00000001` |
| 4 | 4 bytes | `image_type` | Format code (1=JPEG, 2=PNG) |
| 8 | variable | `image_data` | Raw image bytes |
</Tab>
<Tab title="TEXT (3)">
| Offset | Size | Field | Description |
|--------|------|-------|-------------|
| 0 | 4 bytes | `type` | `0x00000003` |
| 4 | 4 bytes | `node_id_len` | Length of node_id string |
| 8 | N bytes | `node_id` | UTF-8 encoded node ID |
| 8+N | variable | `text` | UTF-8 encoded progress text |
</Tab>
<Tab title="PREVIEW_WITH_METADATA (4)">
| Offset | Size | Field | Description |
|--------|------|-------|-------------|
| 0 | 4 bytes | `type` | `0x00000004` |
| 4 | 4 bytes | `metadata_len` | Length of metadata JSON |
| 8 | N bytes | `metadata` | UTF-8 JSON (see below) |
| 8+N | variable | `image_data` | Raw JPEG/PNG bytes |
**Metadata JSON structure:**
```json
{
"node_id": "3",
"display_node_id": "3",
"real_node_id": "3",
"prompt_id": "abc-123",
"parent_node_id": null
}
```
</Tab>
</Tabs>
<Note>
See the [OpenAPI Specification](/development/cloud/openapi) for complete schema definitions of each JSON message type.
</Note>
---
## Downloading Outputs
Retrieve generated files after job completion.
<DownloadOutputs />
---
## Complete End-to-End Example
Here's a full example that ties everything together:
<CodeGroup>
```typescript TypeScript
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",
});
// Get redirect URL (don't follow to avoid sending auth to storage)
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")!;
// Fetch from signed URL without auth headers
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(`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();
```
```python Python
import os
import requests
import json
import asyncio
import aiohttp
import uuid
BASE_URL = "https://cloud.comfy.org"
API_KEY = os.environ["COMFY_CLOUD_API_KEY"]
def get_headers():
return {"X-API-Key": API_KEY, "Content-Type": "application/json"}
def upload_image(file_path: str) -> dict:
"""Upload an image and return the reference for use in workflows."""
with open(file_path, "rb") as f:
response = requests.post(
f"{BASE_URL}/api/upload/image",
headers={"X-API-Key": API_KEY},
files={"image": f},
data={"type": "input", "overwrite": "true"}
)
response.raise_for_status()
return response.json()
def submit_workflow(workflow: dict) -> str:
"""Submit workflow and return prompt_id."""
response = requests.post(
f"{BASE_URL}/api/prompt",
headers=get_headers(),
json={"prompt": workflow}
)
response.raise_for_status()
return response.json()["prompt_id"]
async def wait_for_completion(prompt_id: str, timeout: float = 300.0) -> dict:
"""Wait for job completion via WebSocket."""
ws_url = BASE_URL.replace("https://", "wss://") + f"/ws?clientId={uuid.uuid4()}&token={API_KEY}"
outputs = {}
async with aiohttp.ClientSession() as session:
async with session.ws_connect(ws_url) as ws:
start = asyncio.get_event_loop().time()
async for msg in ws:
if asyncio.get_event_loop().time() - start > timeout:
raise TimeoutError("Job timed out")
if msg.type != aiohttp.WSMsgType.TEXT:
continue
data = json.loads(msg.data)
if data.get("data", {}).get("prompt_id") != prompt_id:
continue
msg_type = data.get("type")
msg_data = data.get("data", {})
if msg_type == "progress":
print(f"Progress: {msg_data.get('value')}/{msg_data.get('max')}")
elif msg_type == "executed":
if output := msg_data.get("output"):
outputs[msg_data["node"]] = output
elif msg_type == "execution_success":
return outputs
elif msg_type == "execution_error":
raise RuntimeError(msg_data.get("exception_message", "Unknown error"))
return outputs
def download_outputs(outputs: dict, output_dir: str):
"""Download all output files."""
os.makedirs(output_dir, exist_ok=True)
for node_outputs in outputs.values():
for key in ["images", "video", "audio"]:
for file_info in node_outputs.get(key, []):
params = {
"filename": file_info["filename"],
"subfolder": file_info.get("subfolder", ""),
"type": file_info.get("type", "output")
}
response = requests.get(f"{BASE_URL}/api/view", headers=get_headers(), params=params)
response.raise_for_status()
path = os.path.join(output_dir, file_info["filename"])
with open(path, "wb") as f:
f.write(response.content)
print(f"Downloaded: {path}")
async def main():
# 1. Load workflow
with open("workflow_api.json") as f:
workflow = json.load(f)
# 2. Optionally upload input images
# image_ref = upload_image("input.png")
# workflow["1"]["inputs"]["image"] = image_ref["name"]
# 3. Modify workflow parameters
workflow["3"]["inputs"]["seed"] = 42
workflow["6"]["inputs"]["text"] = "a beautiful sunset over mountains"
# 4. Submit workflow
prompt_id = submit_workflow(workflow)
print(f"Job submitted: {prompt_id}")
# 5. Wait for completion with progress
outputs = await wait_for_completion(prompt_id)
print(f"Job completed! Found {len(outputs)} output nodes")
# 6. Download outputs
download_outputs(outputs, "./outputs")
print("Done!")
if __name__ == "__main__":
asyncio.run(main())
```
</CodeGroup>
---
## Queue Management
### Get Queue Status
<CodeGroup>
```bash curl
curl -X GET "$BASE_URL/api/queue" \
-H "X-API-Key: $COMFY_CLOUD_API_KEY"
```
```typescript TypeScript
async function getQueue(): Promise<{
queue_running: any[];
queue_pending: any[];
}> {
const response = await fetch(`${BASE_URL}/api/queue`, {
headers: getHeaders(),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
const queue = await getQueue();
console.log(`Running: ${queue.queue_running.length}`);
console.log(`Pending: ${queue.queue_pending.length}`);
```
```python Python
def get_queue():
"""Get current queue status."""
response = requests.get(
f"{BASE_URL}/api/queue",
headers=get_headers()
)
response.raise_for_status()
return response.json()
queue = get_queue()
print(f"Running: {len(queue.get('queue_running', []))}")
print(f"Pending: {len(queue.get('queue_pending', []))}")
```
</CodeGroup>
### Cancel a Job
<CodeGroup>
```bash curl
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"]}'
```
```typescript TypeScript
async function cancelJob(promptId: string): Promise<void> {
const response = await fetch(`${BASE_URL}/api/queue`, {
method: "POST",
headers: getHeaders(),
body: JSON.stringify({ delete: [promptId] }),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
}
```
```python Python
def cancel_job(prompt_id: str):
"""Cancel a pending or running job."""
response = requests.post(
f"{BASE_URL}/api/queue",
headers=get_headers(),
json={"delete": [prompt_id]}
)
response.raise_for_status()
return response.json()
```
</CodeGroup>
### Interrupt Current Execution
<CodeGroup>
```bash curl
curl -X POST "$BASE_URL/api/interrupt" \
-H "X-API-Key: $COMFY_CLOUD_API_KEY"
```
```typescript TypeScript
async function interrupt(): Promise<void> {
const response = await fetch(`${BASE_URL}/api/interrupt`, {
method: "POST",
headers: getHeaders(),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
}
```
```python Python
def interrupt():
"""Interrupt the currently running job."""
response = requests.post(
f"{BASE_URL}/api/interrupt",
headers=get_headers()
)
response.raise_for_status()
```
</CodeGroup>
---
## Error Handling
### HTTP Errors
REST API endpoints return standard HTTP status codes:
| Status | Description |
|--------|-------------|
| `400` | Invalid request (bad workflow, missing fields) |
| `401` | Unauthorized (invalid or missing API key) |
| `402` | Insufficient credits |
| `429` | Subscription inactive |
| `500` | Internal server error |
### Execution Errors
During workflow execution, errors are delivered via the `execution_error` WebSocket message. The `exception_type` field identifies the error category:
| Exception Type | Description |
|----------------|-------------|
| `ValidationError` | Invalid workflow or inputs |
| `ModelDownloadError` | Required model not available or failed to download |
| `ImageDownloadError` | Failed to download input image from URL |
| `OOMError` | Out of GPU memory |
| `InsufficientFundsError` | Account balance too low (for Partner Nodes) |
| `InactiveSubscriptionError` | Subscription not active |