リンクをコピーしました

Comfy Cloudでワークフローを組み、色々遊んでいる日々が続いています。
ブラウザで一つずつ実行するのは便利ですが、スクリプトやアプリから同じワークフローを回したいときは API が必要になります。
Cloud API Overview をベースに、Postman と Python で実際に通した流れを残します。

Comfy Cloud の公式サイト

※ 本記事には ComfyUI のアフィリエイトリンク(招待リンク)を含みます。


前提

項目内容
プランAPI 実行は有料プラン(Standard / Creator / Pro)が必要。Free 枠では API でワークフローを実行できません(公式ドキュメント
API の性質Experimental API。エンドポイントやレスポンス形式が変わる可能性があります
クレジットWeb UI と 同じ月間クレジットから消費されます
ベース URLhttps://cloud.comfy.org
認証すべてのリクエストに X-API-Key ヘッダーが必要

まだアカウントがない場合は、下記の招待リンクから登録できます。

👉 Comfy Cloud に登録する(招待リンク)

公式 Quick Start の流れは次の 4 つです。

  1. ワークフローを API 形式で用意する
  2. POST /api/prompt でジョブを投入する
  3. ポーリングまたは WebSocket で完了を待つ
  4. /api/view(またはジョブ詳細 API)で出力を取る
flowchart LR
  A[ブラウザでワークフロー作成] --> B[API 形式でエクスポート]
  B --> C["POST /api/prompt"]
  C --> D[進捗監視]
  D --> E["/api/view でダウンロード"]

ワークフローを API 形式で用意する

まずはブラウザの Comfy Cloud で、テンプレートからワークフローを用意します。
今回は 画像カテゴリの Z-Image-Turbo(テキストから画像)をベースに、シンプルな test-api ワークフローを使いました。

テンプレート一覧から画像系を選ぶ画面API 向けテンプレートのワークフロー例

注意するところはこちらです。

  • SaveImage ノードまで繋がっていること
  • 差し替えたい値(プロンプト、seed など)がどのノードの inputs
  • Partner Nodes を使うなら、ブラウザ上で一度正常に動くこと

Cloud API に渡す JSON は、通常の「保存形式」とは別物です。
Workflow API Format によると、API 形式で書き出す必要があります。

観点保存形式(Save)API 形式(Export Workflow API)
メニューFile → SaveFile → Export Workflow (API)
ノードのキータイトルやラベル数値のノード ID
UI 情報位置・色・グループなど含む除外される
用途エディタで再編集API 投入用

エディタで ファイル → エクスポート (API)(英語 UI なら File → Export Workflow (API))を選び、test-api.json として保存しました。

ファイルメニューからエクスポート (API) を選ぶ画面

今回の test-api の中身は次のとおりです。

{
  "9": {
    "inputs": {
      "filename_prefix": "test_api",
      "images": ["57:8", 0]
    },
    "class_type": "SaveImage",
    "_meta": {
      "title": "画像を保存"
    }
  },
  "62": {
    "inputs": {
      "filename_prefix": "ComfyUI",
      "format": "png",
      "format.bit_depth": "8-bit",
      "format.input_color_space": "sRGB"
    },
    "class_type": "SaveImageAdvanced",
    "_meta": {
      "title": "Save Image (Advanced)"
    }
  },
  "57:30": {
    "inputs": {
      "clip_name": "qwen_3_4b.safetensors",
      "type": "lumina2",
      "device": "default"
    },
    "class_type": "CLIPLoader",
    "_meta": {
      "title": "CLIPを読み込む"
    }
  },
  "57:29": {
    "inputs": {
      "vae_name": "ae.safetensors"
    },
    "class_type": "VAELoader",
    "_meta": {
      "title": "VAEを読み込む"
    }
  },
  "57:33": {
    "inputs": {
      "conditioning": ["57:27", 0]
    },
    "class_type": "ConditioningZeroOut",
    "_meta": {
      "title": "条件付けゼロアウト"
    }
  },
  "57:8": {
    "inputs": {
      "samples": ["57:3", 0],
      "vae": ["57:29", 0]
    },
    "class_type": "VAEDecode",
    "_meta": {
      "title": "VAEデコード"
    }
  },
  "57:28": {
    "inputs": {
      "unet_name": "z_image_turbo_bf16.safetensors",
      "weight_dtype": "default"
    },
    "class_type": "UNETLoader",
    "_meta": {
      "title": "拡散モデルを読み込む"
    }
  },
  "57:27": {
    "inputs": {
      "text": "input text",
      "clip": ["57:30", 0]
    },
    "class_type": "CLIPTextEncode",
    "_meta": {
      "title": "CLIPテキストエンコード(プロンプト)"
    }
  },
  "57:13": {
    "inputs": {
      "width": 1024,
      "height": 1024,
      "batch_size": 1
    },
    "class_type": "EmptySD3LatentImage",
    "_meta": {
      "title": "空のSD3潜在画像"
    }
  },
  "57:11": {
    "inputs": {
      "shift": 3,
      "model": ["57:28", 0]
    },
    "class_type": "ModelSamplingAuraFlow",
    "_meta": {
      "title": "モデルサンプリングオーラフロー"
    }
  },
  "57:3": {
    "inputs": {
      "seed": 0,
      "steps": 8,
      "cfg": 1,
      "sampler_name": "res_multistep",
      "scheduler": "simple",
      "denoise": 1,
      "model": ["57:11", 0],
      "positive": ["57:27", 0],
      "negative": ["57:33", 0],
      "latent_image": ["57:13", 0]
    },
    "class_type": "KSampler",
    "_meta": {
      "title": "Kサンプラー"
    }
  }
}

実際にプログラマティックに入力などをいじるならノードはだいたいここです。

やりたいことノード IDフィールド
プロンプト変更"57:27"inputs.text
seed 変更"57:3"inputs.seed
出力プレフィックス"9"inputs.filename_prefix

API キーを取ってジョブを送る

認証には Comfy Platform の API キーを使います。
手順は Getting an API Key にあります。

  1. platform.comfy.org/login にログイン
  2. API Keys+ 新しいAPIキー
  3. 名前を入れて 生成
  4. 表示されたキーをすぐに保存(再表示不可)

ComfyUI APIキー一覧と「新しいAPIキー」ボタン新しいAPIキーを生成するダイアログ生成直後のキー表示(一度だけ表示される)

export COMFY_CLOUD_API_KEY="comfyui-xxxxxxxxxxxx"
export BASE_URL="https://cloud.comfy.org"

最初の確認は GET /api/user です。僕は Postman から始めました。

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

200 OK{"id":"...","status":"active"} が返れば、キーは生きています。
以降の Postman 操作も、Authorization は同じ API Key → Header の X-API-Key です。

Postman で GET /api/user が 200 OK になった画面


POST /api/prompt で投入する

公式 Quick Start どおり、POST /api/prompt に API 形式の JSON を prompt フィールドで渡します。

つまずきやすいのは、test-api.json を Body のルートにそのまま貼ってしまうこと。
必ず {"prompt": { ...test-api.json の中身... }} にします。

curl -X POST "$BASE_URL/api/prompt" \
  -H "X-API-Key: $COMFY_CLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": '"$(cat test-api.json)"'}'

成功すると、だいたい次の形で prompt_id が返ります。これが以降のジョブ ID です。

{
  "prompt_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "number": 1,
  "node_errors": {}
}

Postman で POST /api/prompt が成功し prompt_id が返った画面

実運用では、JSON を読み込んで seed やプロンプトを差し替えてから送るパターンが多いです。

import os
import json
import requests

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

with open("test-api.json") as f:
    workflow = json.load(f)

# ノード ID は test-api.json に合わせる(ワークフローごとに違う)
workflow["57:3"]["inputs"]["seed"] = 42
workflow["57:27"]["inputs"]["text"] = "a cat wearing sunglasses, studio photo"

response = requests.post(
    f"{BASE_URL}/api/prompt",
    headers=get_headers(),
    json={"prompt": workflow},
)
response.raise_for_status()

prompt_id = response.json()["prompt_id"]
print(f"Job submitted: {prompt_id}")

進捗を待つ(実測の status)

ジョブは非同期です。
手軽なのは GET /api/job/{prompt_id}/status のポーリングです。

status意味
waiting_to_dispatchディスパッチ待ち
pendingキュー待ち
in_progress実行中
success / completed成功(実APIは success、Quick Start 例は completed
error / failed失敗
cancelledキャンセル

僕が Postman で叩いた成功時のレスポンスは、公式 OpenAPI の JobStatusResponse どおり次の形でした。

{
  "id": "1db59ce7-330d-4218-b889-ce92cbd63e5d",
  "status": "success",
  "created_at": "2026-07-15T10:10:35.257831Z",
  "updated_at": "2026-07-15T10:10:41.014223Z",
  "last_state_update": "2026-07-15T10:10:41.014223Z",
  "assigned_inference": "10.4.35.14:8080",
  "error_message": null
}

Postman で GET /api/job/…/status が success になった画面

ここで大事なのは、status API にはファイル名が入らないことです。
完了判定には足りますが、ダウンロード用の filename は次のジョブ詳細から取ります。

import time

def poll_for_completion(prompt_id: str, timeout: int = 300, poll_interval: float = 2.0) -> None:
    start_time = time.time()

    while time.time() - start_time < timeout:
        status_res = requests.get(
            f"{BASE_URL}/api/job/{prompt_id}/status",
            headers=get_headers(),
        )
        status_res.raise_for_status()
        status = status_res.json()["status"]

        if status in ("success", "completed"):
            return
        if status in ("error", "failed", "cancelled"):
            raise RuntimeError(f"Job failed with status: {status}")

        time.sleep(poll_interval)

    raise TimeoutError(f"Job {prompt_id} did not complete within {timeout}s")

poll_for_completion(prompt_id)

進捗バーやノード単位のログが欲しいときは WebSocket です。

wss://cloud.comfy.org/ws?clientId={uuid}&token={api_key}

Postman で接続したとき、最初に返ってきたのは次でした。

{
  "type": "status",
  "data": {
    "status": {
      "exec_info": {
        "queue_remaining": 0
      }
    },
    "sid": "31d0726d-635d-4a94-aaf5-1c862db38bbc"
  }
}

キュー状況はリアルタイムで見えます。
ただ、画像の filename まではここからは見えませんでした。
成果物を取るなら、次のジョブ詳細 API の方がわかりやすかったです。


出力を取る(実測の outputs)

ダウンロードには filename(と必要なら subfolder / type が必要です。
取得先は GET /api/jobs/{prompt_id}Get full job details)。
さっきの /api/job/.../status とはパスが違います(job 単数 vs jobs 複数)。

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

僕が実際に叩いたレスポンスは、ざっくり次の形でした(長い workflowexecution_status.messages は省略)。

{
  "id": "1db59ce7-330d-4218-b889-ce92cbd63e5d",
  "status": "completed",
  "outputs_count": 1,
  "execution_status": {
    "completed": true,
    "status_str": "success"
  },
  "outputs": {
    "9": {
      "images": [
        {
          "display_name": "test_api_00001_.png",
          "filename": "7e4a58ad6987c5ab0e833e1933ae88409f2d16cafcb46258dd917b55b321a5f5.png",
          "subfolder": "",
          "type": "output"
        }
      ]
    }
  },
  "preview_output": {
    "filename": "7e4a58ad6987c5ab0e833e1933ae88409f2d16cafcb46258dd917b55b321a5f5.png",
    "mediaType": "images",
    "nodeId": "9",
    "subfolder": "",
    "type": "output"
  }
}

Postman でジョブ詳細の outputs(filename / display_name)を確認した画面

フィールド中身の例用途
filenameハッシュっぽい長い .png/api/view に渡す実体名
display_nametest_api_00001_.png人向けの表示名(SaveImage の filename_prefix 由来)
subfolder""指定していなければ空
typeoutput/api/view にも渡す

/api/view には必ず filename(ハッシュ側) を使います。display_name を渡すと取れません。

curl -L "$BASE_URL/api/view?filename=7e4a58ad6987c5ab0e833e1933ae88409f2d16cafcb46258dd917b55b321a5f5.png&subfolder=&type=output" \
  -H "X-API-Key: $COMFY_CLOUD_API_KEY" \
  -o test_api_00001_.png

Postman で /api/view から画像プレビューを取得した画面

Python なら、ジョブ詳細の outputs を回して保存するだけです。

job_res = requests.get(
    f"{BASE_URL}/api/jobs/{prompt_id}",
    headers=get_headers(),
)
job_res.raise_for_status()
outputs = job_res.json()["outputs"]

for node_outputs in outputs.values():
    for file_info in node_outputs.get("images", []):
        params = {
            "filename": file_info["filename"],
            "subfolder": file_info.get("subfolder", ""),
            "type": file_info.get("type", "output"),
        }
        view_res = requests.get(
            f"{BASE_URL}/api/view",
            headers=get_headers(),
            params=params,
        )
        view_res.raise_for_status()
        save_as = file_info.get("display_name") or file_info["filename"]
        with open(save_as, "wb") as f:
            f.write(view_res.content)
        print(f"Downloaded: {save_as}")

API 経由で回したあと、ブラウザ側のワークフロー画面でも同じ結果が確認できます。
SaveImage のプレフィックス test_api から、サイドバーに test_api_00001_ として並びました。

API 実行後、ブラウザの SaveImage に結果が表示された画面

ブラウザ UI で Run したときと同じ品質の PNG が手元に落ちれば、一連の流れは通っています。


1本にした Python サンプル

上の流れを 1 本にすると、だいたい次の形になります(公式 Complete Example をベースに整理)。

import os
import json
import time
import requests

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 main():
    with open("test-api.json") as f:
        workflow = json.load(f)

    workflow["57:3"]["inputs"]["seed"] = 42
    workflow["57:27"]["inputs"]["text"] = "a beautiful sunset"

    response = requests.post(
        f"{BASE_URL}/api/prompt",
        headers=get_headers(),
        json={"prompt": workflow},
    )
    response.raise_for_status()
    prompt_id = response.json()["prompt_id"]
    print(f"Job submitted: {prompt_id}")

    while True:
        status_res = requests.get(
            f"{BASE_URL}/api/job/{prompt_id}/status",
            headers=get_headers(),
        )
        status_res.raise_for_status()
        status = status_res.json()["status"]

        if status in ("success", "completed"):
            break
        if status in ("error", "failed", "cancelled"):
            raise RuntimeError(f"Job {status}")
        time.sleep(2)

    job_res = requests.get(
        f"{BASE_URL}/api/jobs/{prompt_id}",
        headers=get_headers(),
    )
    job_res.raise_for_status()
    outputs = job_res.json()["outputs"]

    for node_outputs in outputs.values():
        for file_info in node_outputs.get("images", []):
            params = {
                "filename": file_info["filename"],
                "subfolder": file_info.get("subfolder", ""),
                "type": "output",
            }
            view_res = requests.get(
                f"{BASE_URL}/api/view",
                headers=get_headers(),
                params=params,
            )
            view_res.raise_for_status()
            save_as = file_info.get("display_name") or file_info["filename"]
            with open(save_as, "wb") as f:
                f.write(view_res.content)
            print(f"Downloaded: {save_as}")

if __name__ == "__main__":
    main()

ローカル ComfyUI 向けの自動化がある場合も、ベース URL と API キーを差し替えるだけでかなり流用しやすいのが、この API の強みでした。


試してみて感じたこと

  • ブラウザで動いたワークフローは、Export Workflow API でそのまま API 化しやすい
  • 入力差し替えは JSON の inputs を書き換えるだけなので、バッチ生成と相性がいい
  • 最初のAPIを使っての確認は Postman で /api/user/api/prompt で試すのが良さげ
  • ジョブ詳細の outputs では filename がハッシュ、display_name が読みやすい名前/api/view にはハッシュ側を渡す
  • Free 枠では API 実行不可なので、検証時点で有料プランが必要
  • Experimental 表記どおり、公式ドキュメントはたまに見直した方がよさそう

並列実行の上限はプラン依存です(Standard 1 / Creator 3 / Pro 5 ジョブ)。
複数プロンプトを一気に投げる場合は 公式の並列サンプル も参考になります。


Comfy Cloud の招待リンク

👉 Comfy Cloud に登録する(招待リンク)

API を本格的に使う場合は、Standard 以上のプランplatform.comfy.org での API キー発行が必要です。


参考リンク


まとめ

公式ドキュメントは揃っていますが、写真が少ないので、初回の疎通記録として残しました。
実測で効いたのは、statusjobs の役割分担と、filename(ハッシュ)と display_name の使い分けです。
自動化や量産に進む前に、まずは 1 枚画像を API で出すところまで通してみるのが近道でした。