同じワークフローで100枚まとめて生成したい。プロンプトだけ差し替えて回したい——そこまで来るとブラウザをクリックしている場合ではなくなる。ComfyUIはHTTPサーバとして動いているので、外からJSONを投げれば実行できる。ただし最初の一投目でほぼ全員がつまずく。画面から保存したワークフローJSONは、そのままでは受け付けてもらえないからだ。
検証環境と方法:ComfyUI 0.3.76/Python 3.12.11/PyTorch 2.10.0+cu130(Windows 11)。サーバを --cpu --port 8189 で起動し、実際にHTTPを叩いた応答をそのまま載せている。ステータスコードとJSONは実測値。測っていない範囲=WebSocketでの進捗受信、モデルを読み込む実際の生成処理、他の版での再現。
ComfyUIはどこを叩けばいい?
ComfyUIは起動すると 127.0.0.1:8188 で待ち受ける(--listen と --port の既定値)。画面が使っているのと同じAPIを、そのまま外から呼べる。
| エンドポイント | 用途 |
|---|---|
POST /prompt |
ワークフローを実行キューに入れる |
GET /prompt |
キューの残り件数を返す |
GET /history/{prompt_id} |
実行結果と出力ファイル名 |
GET /view |
生成された画像そのものを取得 |
GET /object_info |
使えるノードと入力仕様の一覧 |
POST /interrupt |
実行中の処理を止める |
このうち実際に叩いて確認したのは /prompt(POST・GET)、/history、/view、/object_info の5つで、/interrupt は生成の中断を伴うため試していない。
/object_info は自分の環境で何が使えるかを返す。検証環境では539種類のノードが並び、たとえば KSampler の必須入力が model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise であることまで読める。他人のワークフローが動かないときは、まずここに目的のノードが載っているかを見る。
いちばん多い失敗:ワークフローJSONをそのまま投げる
画面の「ワークフローを保存」で出てくるJSONと、APIが受け取るJSONは別物だ。実際に前者を投げると、こう返ってくる。
HTTP 400
{"error": {"type": "invalid_prompt",
"message": "Cannot execute because a node is missing the class_type property.",
"details": "Node ID '#id'"},
"node_errors": {}}
details が Node ID '#id' なら、まずこの取り違えを疑う。画面用のJSONはトップレベルが id / nodes / links …という構造で、APIはそのキーをノードIDだと思って読みにいく。だから存在しない「idという名前のノード」について文句を言ってくる。
ただしこの出方は取り違えに固有ではない。API形式のJSONでノードIDをたまたま id にし、そのノードに class_type を書き忘れても、まったく同じ応答になることを確認した。目印として使ってよいが、証明にはならない——最終的にはボディの構造を見る。
APIが求めるのはノードIDをキーにした辞書で、各値が class_type と inputs を持つ形になる。画面用のJSONは構造からして違い、同梱テンプレート4本で確かめたところいずれも class_type を持つノードが0個で、4本とも同じ400が返った。メニューからAPI形式で書き出したものを使う。
{
"prompt": {
"1": {"class_type": "EmptyImage",
"inputs": {"width": 64, "height": 64, "batch_size": 1, "color": 8421504}},
"2": {"class_type": "SaveImage",
"inputs": {"images": ["1", 0], "filename_prefix": "myrun"}}
},
"client_id": "my-client-001"
}
入力の ["1", 0] がノード1の0番目の出力を繋ぐという意味で、これが画面上の線に相当する。
最小の往復:投げて、待って、受け取る
上のJSONをPOSTすると、実測ではこう返る。
HTTP 200
{"prompt_id": "f2625144-0404-4b4b-8da3-25fa35dd5b9e", "number": 6, "node_errors": {}}
返ってくるのは受付番号であって、結果ではない。実行は非同期なので、prompt_id を持って結果を取りにいく。
GET /history/f2625144-0404-4b4b-8da3-25fa35dd5b9e
{"f2625144-...": {
"status": {"status_str": "success", "completed": true},
"outputs": {"2": {"images": [
{"filename": "myrun_00001_.png", "subfolder": "", "type": "output"}]}}}}
outputs のキー "2" はSaveImageノードのIDだ。ここで得たファイル名を /view に渡せば画像そのものが返る——検証では GET /view?filename=myrun_00001_.png&subfolder=&type=output で image/png の64×64が取れた。ポーリングの間隔は、GET /prompt が返す {"exec_info": {"queue_remaining": 0}} を見て決めるのが素直だ。
エラーは node_errors を読む
内容の検証で弾かれたときは400で、error.type に理由の種別が入る。実測で確認できたのは次の4つだ。
error.type |
何が起きたか |
|---|---|
no_prompt |
ボディに prompt キーが無い |
invalid_prompt |
class_type が無い/そのノードが存在しない |
prompt_no_outputs |
出力ノード(SaveImage等)が1つも無い |
prompt_outputs_failed_validation |
入力の検証に落ちた。詳細は node_errors |
ただし失敗が全部400になるわけではない。ボディがJSONとして壊れている場合と、prompt が辞書でない場合(配列を渡すなど)は、実測で500 Internal Server Errorが返り、error.type の付いたJSONは返ってこない。クライアント側ではステータスコードで分岐してから本文を読む——500のときにJSONを期待すると、そこで例外になる。参考までに、存在しないパスは404、対応していないメソッドは405だった。
4つのうち最後の1つだけ性質が違い、node_errors にノードIDごとの理由が入る。たとえばLATENTを画像入力に繋ぐと、こう返る。
"node_errors": {"2": {"errors": [{
"type": "return_type_mismatch",
"details": "images, received_type(LATENT) mismatch input_type(IMAGE)",
"extra_info": {"input_name": "images", "received_type": "LATENT",
"linked_node": ["1", 0]}}]}}
どのノードの、どの入力が、何を受け取って落ちたかが全部入っている。自動化する側は error.message ではなく node_errors をログに残しておくと、あとで原因を追える。ComfyUIのコードにはこのほか required_input_missing、value_not_in_list、value_bigger_than_max、dependency_cycle といった種別も定義されている。
よくある質問
API形式のJSONはどこから書き出しますか?
class_type と inputs を持ちます。通常保存したJSONは画面のレイアウト情報(座標やサイズ)を含む別形式で、APIには渡せません。ワークフローそのものの整理や共有についてはワークフローの管理・整理術で扱っています。他人のワークフローを投げたら「node does not exist」と出ます
GET /object_info を叩いて一覧に含まれているかを確認し、無ければ導入します。エラーの details に Node ID '#3' のように該当ノードのIDが入るので、どこを直せばよいかはすぐ分かります。導入方法はComfyUI-Managerの使い方にまとめています。生成の進捗をリアルタイムに受け取れますか?
/ws でWebSocketを提供しています。注意点は名前の綴りで、接続時のクエリパラメータは clientId(キャメルケース)——POSTのボディに入れる client_id とは違います。同じ値で接続すると、そのクライアント宛に status / executing / executed / execution_error が送られる実装になっています。ただしこの記事ではWebSocketの受信を検証していません(ソースを読んだ範囲の話です)。結果を取るだけなら、GET /prompt の queue_remaining を見ながら /history をポーリングするほうが簡単です。まとめ:詰まるのは形式のところだけ
- 既定の待ち受けは
127.0.0.1:8188。画面と同じAPIを外から呼べる - 画面から保存したJSONは投げられない。「API形式で書き出し」を使う
detailsのNode ID '#id'は有力な目印だが決め手ではない。最後はボディの構造を見るPOST /promptが返すのは受付番号。結果は/history/{prompt_id}から取る- 検証落ちは400/JSONが壊れていると500。原因の粒度が要るときは
node_errorsを見る
形式さえ合ってしまえば、あとは inputs の値を書き換えて回すだけの作業になる。プロンプトの差し替えもシードの総当たりも、辞書を1か所いじるループで書ける。

コメント