Как подключиться к API ComfyUI: первый запрос строится из четырёх шагов. Запустите локальный сервер, проверьте адрес через /system_stats, экспортируйте workflow в API-формате, отправьте его методом POST на /prompt и дождитесь готовности через /history/{prompt_id}. Файл результата забирается с /view.
Граница локального сервера и облачных API
Локальный сервер ComfyUI поднимается на вашей машине командой python main.py. Порт по умолчанию — 8188, его можно сменить флагом --port 8288. Для доступа с других устройств в сети добавьте --listen 0.0.0.0. Для работы только на своём компьютере флаг --listen не нужен. Открывать браузер необязательно: python main.py --disable-auto-launch запускает сервер без автоматического открытия браузера. Так описывает запуск официальная документация ComfyUI.
Локальный пример использует адрес 127.0.0.1:8188. Для облачного API отдельно сверяют адрес, способ авторизации и формат запроса в документации его владельца.
Различие проявляется в первом же действии. Локально вы проверяете, что сервер отвечает на вашем компьютере. В облаке вы сначала получаете ключ в панели провайдера, затем формируете запрос к его домену. Если в примере из интернета встречается переменная вида API_KEY, это признак облачного сценария. Локальный пример обходится без неё.
Второе различие — судьба файлов. Локальный сервер складывает результаты в свою папку вывода, а путь к файлу возвращает в ответе. Облачный сервис отдаёт ссылку на хранилище провайдера. Поэтому инструкции по скачиванию результата у двух сценариев разные, и переносить их друг в друга не стоит.
Экспорт настоящего workflow в API-формате
Обычный файл workflow, который вы сохраняете из интерфейса, для API не подходит. В нём лежит описание холста: координаты узлов, связи, оформление. Серверу нужен другой формат — словарь, где ключом служит идентификатор узла, а значением объект с полями class_type и inputs. Официальная документация ComfyUI называет такой файл workflow, экспортированным в API-формате.
Порядок действий в интерфейсе: соберите граф, убедитесь, что он выполняется, затем выберите в меню сохранение в API-формате. Полученный JSON и есть тело будущего запроса. В официальном примере basic_api_example.py такой JSON показан целиком: узлы KSampler, CheckpointLoaderSimple, EmptyLatentImage, CLIPTextEncode, VAEDecode, SaveImage. У каждого — свой class_type и набор inputs.
Структура узла выглядит так: "3": {"class_type": "KSampler", "inputs": {...}}. Ссылки на другие узлы записываются парой «идентификатор, индекс выхода»: "model": ["4", 0] означает, что вход model берёт нулевой выход узла 4. Индекс выхода важен: у загрузчика модели выход 0 — сама модель, выход 1 — CLIP, выход 2 — VAE. Ошибка в индексе даёт неверный результат или отказ валидации.
Перед отправкой полезно заменить изменяемые значения. В официальном примере перед постановкой задания меняются seed и текст положительного промпта: prompt["3"]["inputs"]["seed"] = 5 и prompt["6"]["inputs"]["text"] = "...". Тот же приём работает для размера изображения, числа шагов, имени модели. Так один и тот же файл обслуживает серию запросов.
Имя модели в CheckpointLoaderSimple должно совпадать с тем, что лежит в папке моделей сервера. В официальном примере указано v1-5-pruned-emaonly.safetensors. Если такого файла у вас нет, подставьте своё имя — список доступных значений отдаёт маршрут /object_info.
Сохраните экспортированный JSON отдельным файлом. Дальше он читается скриптом, правится в нужных полях и уходит на сервер. Такой файл удобно версионировать: изменение графа в интерфейсе даёт новый экспорт, а скрипт остаётся тем же.
Предпроверка локального сервера без запуска генерации
Перед первым заданием убедитесь, что сервер жив и отвечает. Официальная справка перечисляет маршруты, которые для этого годятся. /system_stats возвращает сведения о системе: версию Python, устройства, объём видеопамяти. /object_info отдаёт описание всех типов узлов. /models возвращает список доступных типов моделей, /models/{folder} — модели в конкретной папке.
Запрос к /system_stats не запускает генерацию и не занимает очередь. Ответ приходит сразу, если сервер слушает адрес. Пустой ответ или отказ соединения означают, что процесс не запущен либо слушает другой порт. Это самый быстрый способ отделить проблему адреса от проблемы задания.
Проверка узлов решает вторую по частоте причину отказов. Если в вашем экспортированном JSON есть узел, которого нет на сервере, валидация вернёт ошибку. Маршрут /object_info показывает, какие типы узлов сервер знает. Сверьте список class_type из своего файла с этим ответом. Расхождение указывает на отсутствующее расширение.
Проверка моделей закрывает третью причину. Маршрут /models/{folder} перечисляет файлы в папке. Сверьте имя из CheckpointLoaderSimple с этим списком. Если имени нет, сервер не сможет загрузить модель, и задание упадёт.
Порядок предпроверки:
- Откройте
/system_statsи убедитесь, что приходит ответ. - Откройте
/object_infoи сверьте типы узлов со своим файлом. - Откройте
/models/checkpointsи сверьте имя модели. - Только после этого отправляйте задание.
Такой порядок экономит время: три дешёвых запроса отсекают большинство отказов до того, как в очередь уйдёт работа.
Постановка workflow через POST /prompt и смысл prompt_id
Задание отправляется методом POST на маршрут /prompt. Тело запроса — JSON с полем prompt, внутри которого лежит ваш экспортированный workflow. Официальная справка ComfyUI описывает поведение так: сервер проверяет задание, ставит его в очередь выполнения и возвращает либо prompt_id вместе с номером позиции в очереди, либо error и node_errors, если проверка не прошла.
prompt_id — это идентификатор вашего задания. Он нужен, чтобы позже запросить результат именно этого запуска. Очередь может содержать несколько заданий, и без идентификатора вы не отличите свой результат от чужого. В официальном примере с WebSocket идентификатор генерируется на стороне клиента и передаётся в теле запроса вместе с client_id. В примере с чистым HTTP сервер возвращает свой идентификатор в ответе.
Поле client_id связывает запрос с конкретным клиентом. Оно используется, когда вы слушаете WebSocket и хотите получать сообщения о ходе своего задания. Для первого HTTP-запроса это поле не обязательно.
Ответ с ошибкой приходит, когда проверка не прошла. Поля error и node_errors указывают, что именно не так. Отсутствующий узел, неизвестное имя модели, несовпадающий тип входа — всё это отлавливается на этом шаге, до выполнения. Разбирать ответ стоит сразу: он называет проблемный узел точнее, чем последующее поведение очереди.
Учебный скрипт ниже собран по официальным маршрутам. Сначала сохраните свой рабочий граф в API-формате как workflow_api.json рядом со скриптом. Выполнение можно прервать с клавиатуры, затем проверить задание по сохранённому идентификатору.
import json
import time
from pathlib import Path
from urllib.request import Request, urlopen
from urllib.parse import urlencode
base = "http://127.0.0.1:8188"
with urlopen(base + "/system_stats") as response:
print("Сервер отвечает:", response.status)
workflow = json.loads(Path("workflow_api.json").read_text(encoding="utf-8"))
request = Request(base + "/prompt", data=json.dumps({"prompt": workflow}).encode("utf-8"),
headers={"Content-Type": "application/json"})
with urlopen(request) as response:
accepted = json.load(response)
prompt_id = accepted["prompt_id"]
print("Задание:", prompt_id)
while True:
with urlopen(base + "/history/" + prompt_id) as response:
history = json.load(response)
if prompt_id in history:
break
time.sleep(1)
images = [image for output in history[prompt_id].get("outputs", {}).values()
for image in output.get("images", [])]
if not images:
raise RuntimeError("В истории нет изображений; проверьте ошибку задания и выходной узел")
for image in images:
query = urlencode({key: image[key] for key in ("filename", "subfolder", "type")})
with urlopen(base + "/view?" + query) as response:
destination = Path(image["filename"]).name
Path(destination).write_bytes(response.read())
print("Сохранено:", destination)
Это учебный пример; редакция генерацию не запускала. Скрипт сохраняет изображения от узлов с выходом images. Для графа с другим типом результата потребуется обработчик соответствующего выхода.
Ожидание завершения и получение файлов результата
После постановки задания нужно дождаться его выполнения. Официальная справка даёт три подхода. Первый — отправить задание и не отслеживать его, забрав результат позже. Второй — слушать WebSocket и по сообщению о завершении запросить историю. Третий — получать изображения прямо через WebSocket с узлом SaveImageWebsocket.
Для первого запроса достаточно второго подхода в упрощённом виде: опрос истории. Маршрут /history/{prompt_id} возвращает историю очереди по конкретному заданию. Когда задание выполнено, в ответе появляется блок outputs с описанием выходных узлов. У узла SaveImage в этом блоке лежит список изображений с полями filename, subfolder, type.
Файл забирается с маршрута /view. Параметры передаются строкой запроса: имя файла, подпапка, тип. В официальном примере это делается через urllib.parse.urlencode. Ответ содержит двоичные данные изображения, которые можно сохранить на диск или открыть в памяти.
Маршрут /history без идентификатора возвращает историю целиком. Маршрут /queue показывает текущее состояние очереди. Маршрут /interrupt останавливает текущее выполнение, /free выгружает модели из памяти. Эти маршруты полезны, когда очередь забита или нужно освободить видеопамять.
Если вы выбрали путь через WebSocket, порядок такой: подключиться к /ws с параметром clientId, отправить задание, ждать сообщения типа executing. Когда в этом сообщении поле node равно None и идентификатор совпадает с вашим, выполнение завершено. Дальше история и файлы забираются теми же маршрутами. Официальная справка перечисляет типы сообщений WebSocket: status, execution_start, execution_cached, executing, progress, executed.
Опрос истории проще в отладке: видно, что задание ещё в очереди, что оно выполняется, что завершилось. Для первого запроса этого достаточно.
Диагностика адреса, отсутствующего узла и модели и ошибки задания
Отказы на первом запросе укладываются в несколько групп. Разберём их по признакам.
| Признак | Вероятная причина | Что проверить |
|---|---|---|
| Соединение отклонено | Сервер не запущен или другой порт | Команду запуска, флаг --port, адрес в скрипте |
| Ответ пустой | Сервер слушает другой интерфейс | Флаг --listen, доступность с вашей машины |
node_errors с именем узла |
Узел отсутствует на сервере | Список типов из /object_info |
| Ошибка загрузки модели | Имени нет в папке моделей | Список из /models/checkpoints |
error без деталей узла |
Несовпадение типов входов | Индексы выходов в ссылках вида ["4", 0] |
| Задание в очереди, результата нет | Очередь занята или выполнение упало | /queue, /history/{prompt_id} |
Файл не найден на /view |
Неверные filename, subfolder, type |
Блок outputs в истории |
Первая строка таблицы закрывает самый частый случай. Скрипт обращается к 127.0.0.1:8188, а сервер поднят на другом порту. Сверьте адрес в скрипте с командой запуска.
Вторая строка — про удалённый доступ. Если вы запускаете сервер на одной машине, а запрос шлёте с другой, нужен флаг --listen 0.0.0.0. Без него сервер принимает подключения только с локального адреса.
Третья и четвёртая строки связаны с содержимым вашего экспорта. Узел из пользовательского расширения на сервере не установлен — валидация это покажет. Имя модели в файле не совпадает с файлом на диске — задание не стартует.
Пятая строка — про ссылки между узлами. Если вход model ссылается на выход 1 вместо 0, тип не сойдётся. Проверьте индексы выходов у загрузчика модели.
Шестая и седьмая строки относятся к этапу после постановки. Задание принято, но результата нет: смотрите /queue и историю. Файл не скачивается: сверьте три поля из блока outputs.
Готовая таблица значений и проверяемый учебный HTTP-пример
Соберём значения первого запроса в одну таблицу.
| Параметр | Значение | Где задаётся |
|---|---|---|
| Адрес сервера | 127.0.0.1:8188 |
Команда запуска, скрипт |
| Порт | 8188, меняется флагом --port |
Команда запуска |
| Внешний доступ | --listen 0.0.0.0 |
Команда запуска |
| Запуск без браузера | --disable-auto-launch |
Команда запуска |
| Маршрут постановки | /prompt, метод POST |
Тело запроса |
| Поле тела | prompt со словарём узлов |
Экспорт API-формата |
| Ответ успеха | prompt_id и номер в очереди |
Ответ сервера |
| Ответ ошибки | error, node_errors |
Ответ сервера |
| Маршрут истории | /history/{prompt_id} |
Опрос результата |
| Маршрут файла | /view с filename, subfolder, type |
Скачивание |
| Проверка системы | /system_stats |
Предпроверка |
| Проверка узлов | /object_info |
Предпроверка |
| Проверка моделей | /models/{folder} |
Предпроверка |
Полный цикл в одном скрипте: прочитать экспортированный JSON, заменить seed и текст, отправить на /prompt, дождаться записи в /history/{prompt_id}, забрать файл с /view. Каждый шаг проверяется отдельным маршрутом, поэтому сбой локализуется быстро.
Готовый промпт для нейросети, который помогает разобрать ошибку валидации:
Ты помогаешь отладить запрос к локальному серверу ComfyUI.
Ниже JSON, который я отправляю методом POST на /prompt, и ответ сервера.
Разбери ответ: назови проблемный узел, объясни причину отказа
и предложи исправление в моём JSON.
Не предлагай менять адрес сервера и не добавляй ключи API.
Мой JSON: [вставьте экспортированный workflow]
Ответ сервера: [вставьте ответ с error или node_errors]
В квадратных скобках подставьте свои данные. Если ошибок нет, попросите модель сверить типы входов у узлов KSampler и CLIPTextEncode с описанием из /object_info.
Частые вопросы
Что такое ComfyUI API и чем он отличается от интерфейса?
API — это те же маршруты, что использует веб-интерфейс, но доступные по HTTP. Интерфейс рисует холст и отправляет задания сам. Через API вы формируете тело запроса в скрипте и получаете ответ в виде JSON. Официальная справка перечисляет маршруты для заданий, очереди, истории, моделей и пользовательских данных.
Какой адрес у локального сервера ComfyUI?
По умолчанию сервер слушает 127.0.0.1:8188. Порт меняется флагом --port, например --port 8288. Для доступа с других машин в сети добавляется флаг --listen 0.0.0.0. Для работы на своём компьютере этот флаг не нужен.
Почему падает скорость при работе через API?
Очередь может содержать несколько заданий, и ваше ждёт своей позиции. Маршрут /free выгружает модели из памяти, после чего следующее задание загружает их заново. Следите за состоянием через /queue и не выгружайте модели между заданиями одной серии.
Что делать, если сервер вернул node_errors?
Ответ называет проблемный узел. Сверьте его class_type со списком из /object_info. Если тип отсутствует, на сервере нет нужного расширения. Если тип есть, проверьте индексы выходов в ссылках вида ["4", 0] и имена моделей.
Как забрать готовое изображение?
Запросите /history/{prompt_id} и найдите блок outputs. У узла SaveImage там лежат поля filename, subfolder, type. Передайте их параметрами на маршрут /view и сохраните двоичный ответ. Для потока в реальном времени есть узел SaveImageWebsocket.
