🕳️

ComfyUI APIの落とし穴 — GUI形式とAPI形式はなぜ違うのか

に公開

はじめに

ComfyUIでワークフローを作り込んでいくと、ある時点で必ずこう思う。

「これ、毎回GUIでポチポチ実行するの面倒だな。APIで自動化したい」

n8nやDifyなどのワークフローツールと連携したい。バッチ処理で100枚回したい。AITuberのパイプラインに組み込みたい。理由は様々だが、行き着く先は同じ — ComfyUIのREST APIを叩くことだ。

ところが、GUIで「Save」したワークフローJSONを/promptエンドポイントにそのまま投げると、何も起きないか、エラーが返ってくる

筆者はWindows環境でComfyUI CLIツールを自作する過程で、この「GUI形式とAPI形式の乖離」に何度もハマった。SetNode/GetNodeがmissing_node_typeエラーで弾かれ、widget_valuesの配列が名前付きパラメータに変換されずrequired_input_missingになり、フロントエンド専用ノードがAPIサーバーに存在しないことを知った。

本記事では、ComfyUIのGUI保存形式API実行形式の構造的な違いと、自動化する際に踏む落とし穴を、実際のワークフローJSONを使って解説する。

検証環境

項目 詳細
GPU NVIDIA GeForce RTX 5090 (32GB VRAM)
OS Windows 11 (26200.7840)
ComfyUI v0.3.40
Python 3.13.12
PyTorch 2.9.1+cu130
CUDA 13.0

2つのJSON形式 — 何が違うのか

ComfyUIには「ワークフローJSON」が2種類ある。これが混乱の元凶だ。

GUI形式(Save で出力される)

GUIの「Save」ボタンで保存される形式。ノードの位置情報、色、リンクの配線情報など、GUIの表示に必要な全情報を含む。

{
  "last_node_id": 291,
  "last_link_id": 291,
  "nodes": [
    {
      "id": 6,
      "type": "EmptyLatentImage",
      "pos": [-463.11, 1127.65],
      "size": [421.85, 106],
      "outputs": [
        {
          "name": "LATENT",
          "type": "LATENT",
          "links": [22]
        }
      ],
      "widgets_values": [720, 1280, 1]
    },
    {
      "id": 38,
      "type": "SetNode",
      "title": "Set_Global_SIZE",
      "inputs": [
        {
          "name": "LATENT",
          "type": "LATENT",
          "link": 22
        }
      ],
      "widgets_values": ["Global_SIZE"]
    }
  ],
  "links": [
    [22, 6, 0, 38, 0, "LATENT"]
  ]
}

特徴:

  • nodes配列にノード一覧(位置、サイズ、色を含む)
  • links配列にノード間の接続情報([link_id, from_node, from_slot, to_node, to_slot, type]
  • widgets_valuesにウィジェットの値が順番で格納された配列(名前なし)
  • フロントエンド専用ノード(SetNode, GetNode, Reroute等)を含む

API形式(/prompt エンドポイントが受け付ける)

ComfyUIのAPIが実際に実行できる形式。GUIの「Save (API Format)」で出力できる(後述)。

{
  "6": {
    "class_type": "EmptyLatentImage",
    "inputs": {
      "width": 720,
      "height": 1280,
      "batch_size": 1
    }
  }
}

特徴:

  • キーがノードID(文字列)、値が {class_type, inputs}
  • inputs名前付きパラメータ"width": 720
  • ノード間のリンクは [ノードID, スロット番号] で表現
  • 位置情報、色、サイズなどGUI情報は一切含まない
  • フロントエンド専用ノードは存在しない

一目でわかる違い

項目 GUI形式 API形式
ノード格納 nodes配列(オブジェクト) フラットなdict(キーがノードID)
リンク情報 links配列(別テーブル) inputs内にインライン (["6", 0])
ウィジェット値 widgets_values順序配列、名前なし inputs名前付きdict
位置・見た目 含む(pos, size, color) 含まない
フロントエンド専用ノード 含む(SetNode, Reroute等) 含まない(サーバーが知らない)
用途 GUIの保存・復元 APIでの実行

この表の太字部分が、自動化しようとした時にハマるポイントだ。

落とし穴 1: widgets_values — 名前のない配列

最初にハマるのがここ。

GUI形式のノードを見てみよう:

{
  "id": 139,
  "type": "KSampler Adv. (Efficient)",
  "widgets_values": [
    "enable", 12345, "fixed", 8, 1, "euler", "simple",
    0, 10000, "disable", "auto", "true"
  ]
}

12個の値が入っている。だがどの値がどのパラメータなのか、この情報だけではわからない

同じノードのAPI形式:

{
  "139": {
    "class_type": "KSampler Adv. (Efficient)",
    "inputs": {
      "add_noise": "enable",
      "noise_seed": 12345,
      "control_after_generate": "fixed",
      "steps": 8,
      "cfg": 1,
      "sampler_name": "euler",
      "scheduler": "simple",
      "start_at_step": 0,
      "end_at_step": 10000,
      "return_with_leftover_noise": "disable",
      "preview_method": "auto",
      "vae_decode": "true"
    }
  }
}

widgets_valuesの配列を名前付きパラメータに変換する必要がある。この対応関係はノードの種類ごとに異なり、公式ドキュメントには書いていない。

解決策: /object_info エンドポイント

ComfyUIサーバーには /object_info というエンドポイントがあり、全ノードタイプの入力定義を返す。

curl http://127.0.0.1:8188/object_info/KSampler

レスポンス(抜粋):

{
  "KSampler": {
    "input": {
      "required": {
        "model": ["MODEL"],
        "positive": ["CONDITIONING"],
        "negative": ["CONDITIONING"],
        "latent_image": ["LATENT"],
        "seed": ["INT", {"default": 0, "min": 0, "max": 18446744073709551615}],
        "control_after_generate": [["fixed", "increment", "decrement", "randomize"]],
        "steps": ["INT", {"default": 20, "min": 1, "max": 10000}],
        "cfg": ["FLOAT", {"default": 8.0}],
        "sampler_name": [["euler", "euler_ancestral", "heun", ...]],
        "scheduler": [["normal", "karras", "exponential", "simple", ...]],
        "denoise": ["FLOAT", {"default": 1.0}]
      }
    }
  }
}

requiredキーの順序widgets_values配列の順序に対応する。ただしリンクで接続されている入力(model, positive, negative, latent_image)はwidgets_valuesには含まれない。

なお、optionalも同様に"input"."optional"キーの下に定義されている。requiredの後にoptionalの入力が続く順序だ。

つまり変換ロジックは:

  1. /object_info/{ノードタイプ名}からノードの入力定義を取得
  2. requiredoptionalの順で入力名リストを作成
  3. リンク接続済みの入力名を除外(値の先頭が["MODEL"]["CONDITIONING"]のように大文字の型名なら、それはリンク入力)
  4. 残った入力名にwidgets_valuesを順番にマッピング

注意: カスタムノードの中にはobject_infoのキー順とwidgets_valuesの順序が一致しないものがある。筆者の経験ではDF_Text_Boxの入力名が Text(大文字T)であることに気づかずrequired_input_missingエラーに悩まされた。

GUIから「Save (API Format)」する方法

実はComfyUI GUIにはAPI形式で保存する機能がある。ただしデフォルトでは非表示だ。

  1. ComfyUI設定画面を開く(右上の歯車アイコン)
  2. 「Enable Dev mode Options」をONにする
  3. メニューに「Save (API Format)」が表示される

これを使えば変換を自分でやる必要はない。ただし、GUIで保存したワークフローをプログラムから動的に書き換えたい場合(プロンプトの差し替え、シード変更、バッチ実行など)は、GUI形式→API形式の変換ロジックが必要になる。

落とし穴 2: フロントエンド専用ノード

GUI形式のワークフローには、ComfyUIサーバーが認識しないノードが含まれている。API形式に含めるとmissing_node_typeエラーになる。

SetNode / GetNode(KJNodes)

これが最も厄介だった。

SetNode/GetNodeはComfyUI-KJNodesが提供するフロントエンド専用のJavaScriptノードだ。Pythonのサーバーサイドには存在しない。

用途: ワークフロー内で値を「名前付き変数」のように使い回す。

EmptyLatentImage → SetNode("Global_SIZE")

GetNode("Global_SIZE") → KSampler A
GetNode("Global_SIZE") → KSampler B
GetNode("Global_SIZE") → KSampler C

GUIでは便利だが、APIサーバーはSetNodeを知らないので、そのまま送ると:

{
  "error": {
    "type": "missing_node_type",
    "message": "Missing node type: SetNode",
    "details": "node '38'"
  }
}

解決策: SetNode/GetNodeのペアを解決して、直接リンクに置き換える。

# 変換前(GUI形式の関係性)
EmptyLatentImage [6] → SetNode [38] ("Global_SIZE")
GetNode [40] ("Global_SIZE") → KSampler [139]

# 変換後(API形式の直接リンク)
KSampler [139] の latent_image = ["6", 0]  # EmptyLatentImageを直接参照

実装上の罠として、同じ名前のSetNodeが複数存在するパターンがある。筆者が遭遇した6モデルT2Iワークフロー(145ノード)では、Global_POSという名前のSetNodeが3つあり、そのうち2つは入力リンクを持たない「出力専用パススルー」だった。名前ベースのレジストリで逆引きする必要がある。

その他のフロントエンド専用ノード

ノード 提供元 役割 API形式での扱い
SetNode / GetNode KJNodes 値の名前付きルーティング 直接リンクに解決
Reroute ComfyUI Core 配線の見た目整理 スキップしてリンクを繋ぎ直す
Note ComfyUI Core メモ書き 完全に無視
PrimitiveNode ComfyUI Core 定数値の提供 値を下流ノードに埋め込む
Fast Groups Muter rgthree ノードグループの有効/無効切替 完全に無視
Fast Groups Bypasser rgthree ノードグループのバイパス切替 完全に無視

これらを含んだままAPIに投げると全てmissing_node_typeエラーになる。自動変換する場合は全てハンドリングが必要だ。

落とし穴 3: リンク参照の形式が違う

GUI形式ではリンクが別テーブル(links配列)に集約されているが、API形式ではノードのinputs内にインラインで記述される。

GUI形式のリンク

{
  "links": [
    [22, 6, 0, 38, 0, "LATENT"]
  ]
}

[link_id, from_node_id, from_slot, to_node_id, to_slot, type]

ノード側ではlinkプロパティでリンクIDを参照する:

{
  "inputs": [{"name": "LATENT", "type": "LATENT", "link": 22}]
}

API形式のリンク

{
  "139": {
    "inputs": {
      "latent_image": ["6", 0],
      "model": ["130", 0]
    }
  }
}

[接続元ノードID(文字列), 出力スロット番号]

変換時にはlinks配列をルックアップテーブル化して、各ノードの入力リンクを解決する必要がある。

# リンクテーブルを構築
link_map = {}
for link in workflow["links"]:
    link_id, from_node, from_slot, to_node, to_slot, link_type = link[:6]
    link_map[link_id] = (from_node, from_slot)

# ノードの入力リンクを解決
for inp in node["inputs"]:
    if inp.get("link") is not None:
        from_node_id, from_slot = link_map[inp["link"]]
        api_inputs[inp["name"]] = [str(from_node_id), from_slot]

実践: curlでComfyUI APIを叩いてみる

ここまでの知識を踏まえて、実際にAPIでワークフローを実行してみよう。

Step 1: サーバーの死活確認

curl http://127.0.0.1:8188/system_stats
{
  "system": {
    "os": "nt",
    "ram_total": 137138364416,
    "ram_free": 104812175360,
    "comfyui_version": "0.3.40"
  },
  "devices": [
    {
      "name": "NVIDIA GeForce RTX 5090",
      "type": "cuda",
      "vram_total": 34089730048,
      "vram_free": 28741345280
    }
  ]
}

Step 2: API形式のプロンプトを送信

curl -X POST http://127.0.0.1:8188/prompt \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": {
      "1": {
        "class_type": "CheckpointLoaderSimple",
        "inputs": {"ckpt_name": "your_model.safetensors"}
      },
      "2": {
        "class_type": "CLIPTextEncode",
        "inputs": {"text": "1girl, cherry blossoms, anime", "clip": ["1", 1]}
      },
      "3": {
        "class_type": "CLIPTextEncode",
        "inputs": {"text": "worst quality, blurry", "clip": ["1", 1]}
      },
      "4": {
        "class_type": "EmptyLatentImage",
        "inputs": {"width": 512, "height": 768, "batch_size": 1}
      },
      "5": {
        "class_type": "KSampler",
        "inputs": {
          "model": ["1", 0], "positive": ["2", 0], "negative": ["3", 0],
          "latent_image": ["4", 0],
          "seed": 42, "steps": 20, "cfg": 7, "sampler_name": "euler",
          "scheduler": "normal", "denoise": 1.0
        }
      },
      "6": {
        "class_type": "VAEDecode",
        "inputs": {"samples": ["5", 0], "vae": ["1", 2]}
      },
      "7": {
        "class_type": "SaveImage",
        "inputs": {"images": ["6", 0], "filename_prefix": "api_test"}
      }
    }
  }'

成功レスポンス:

{"prompt_id": "da8ef027-1753-4ab9-b7f0-08621c80a489", "number": 6}

Step 3: 実行結果を取得

curl http://127.0.0.1:8188/history/da8ef027-1753-4ab9-b7f0-08621c80a489

Step 4: WebSocketで進捗を監視(オプション)

リアルタイムで実行進捗を見たい場合はWebSocket接続を使う:

ws://127.0.0.1:8188/ws?clientId=my-client-001

受信するメッセージ:

{"type": "execution_start", "data": {"prompt_id": "..."}}
{"type": "execution_cached", "data": {"nodes": ["1", "2"]}}
{"type": "executing", "data": {"node": "5"}}
{"type": "progress", "data": {"value": 5, "max": 20}}
{"type": "executed", "data": {"node": "7", "output": {"images": [...]}}}

よくあるエラーと対処

エラー 原因 対処
missing_node_type: SetNode フロントエンド専用ノードをAPI形式に含めた SetNode/GetNodeを解決して直接リンクに置換
required_input_missing widgets_valuesが名前付きに変換されていない /object_infoで入力定義を取得し正しくマッピング
prompt is not valid JSON GUI形式のJSONをそのまま送った API形式に変換してから送信
400 Bad Request(node_errors リンク先のノードIDが存在しない フロントエンドノード除去時にリンクが切れている
黒い画像が出力される ノード設定は正しいがパラメータの組み合わせが不適切 KSamplerの設定(steps, cfg, sampler)を見直す

まとめ

ComfyUI APIを使う上で知っておくべきポイント:

  1. GUI保存形式とAPI実行形式は別物。GUIのJSONをそのままAPIに投げても動かない
  2. widgets_valuesは名前のない配列/object_infoエンドポイントで入力定義を取得して名前付きパラメータに変換する
  3. フロントエンド専用ノードはサーバーが知らない。SetNode/GetNode, Reroute, Note等はAPI形式から除去し、リンクを解決する必要がある
  4. API形式を手軽に得るなら「Dev mode」を有効化して「Save (API Format)」を使う。ただしプログラムから動的に操作したい場合は変換ロジックが必要

これらの知見は、筆者がComfyUI CLIツールを自作する過程で得たものだ。次回の記事では、これらの問題を吸収するCLIツールをPython + Typer + httpxで実装した話を書く。


📝 本記事の内容は RTX 5090 (32GB VRAM) / Windows 11 環境で筆者が実際に検証しています。執筆にはClaude Codeを補助利用しました。


シリーズ記事:

  1. ComfyUI APIの落とし穴 — GUI形式とAPI形式はなぜ違うのか(本記事)
  2. ComfyUI CLIを自作してワークフロー実行を自動化する(予定)
  3. Reddit発6モデルT2Iワークフローを解析・最適化した話(予定)
  4. RTX 5090でNVFP4量子化を試した — T2VとI2Vで品質が全然違う件(予定)

GitHubで編集を提案

Discussion