Automatizar la generación de imágenes con la API de ComfyUI

"La documentación oficial describe la validación y cola mediante /prompt, además de /ws, /history, /view, /queue y /interrupt."
Ya tienes un workflow estable de ComfyUI y necesitas cuatro imágenes principales para cada uno de 200 productos. Cambiar prompt y seed manualmente en cada ejecución no es viable. El primer POST a /prompt devuelve node_errors porque el archivo no está en API format. Al terminar, /history solo muestra filename y subfolder; el archivo real se descarga mediante /view.
Pasar de la GUI a una producción con scripts exige exportar el workflow correcto, esperar por WebSocket sin sondeos ciegos y controlar concurrencia y VRAM para evitar OOM. A continuación se incluyen un script mínimo, parámetros, estrategias de cola y una lista de ingeniería backend.
Acceso a la API local de ComfyUI Server
Inicio por CLI y puerto predeterminado
ComfyUI escucha en 127.0.0.1:8188 de forma predeterminada. El inicio normal basta para procesos locales. Especifica una dirección de escucha solo si otro equipo de la LAN debe conectarse:
# Local access only
python main.py --port 8188
# Allow LAN access; also configure a firewall, authentication, or a reverse proxy
python main.py --listen 0.0.0.0 --port 8188
Cuando la terminal muestre Starting server, abre http://127.0.0.1:8188. Si aparece la interfaz de ComfyUI, el servicio está listo.
Si falta VRAM, prueba --lowvram, --novram o, como último recurso lento, --cpu, según la versión. Con margen amplio también puedes evaluar --highvram. No asignes estas opciones a una capacidad fija: modelo, precisión, VAE y workflow cambian el pico. Revisa python main.py --help y la documentación oficial actual.
Rutas principales de la API
Las rutas locales se definen en server.py. Un script suele necesitar estos endpoints:
| Ruta | Función | Parámetros/respuesta |
|---|---|---|
/prompt | Validar un workflow y añadirlo a la cola | POST {"prompt": workflow_dict, "client_id": "..."}; devuelve prompt_id o node_errors |
/history/{prompt_id} | Consultar historial y metadata | GET; outputs incluye filename, subfolder y type |
/view?filename=...&subfolder=...&type=... | Descargar un archivo | GET; devuelve datos binarios |
/ws | Recibir estado por WebSocket | ws://127.0.0.1:8188/ws?clientId=... |
/queue | Consultar la cola | GET; devuelve tareas pendientes |
/interrupt | Interrumpir la ejecución actual | POST; útil para timeouts |
/upload/image | Subir una imagen de entrada | POST multipart/form-data |
/object_info | Consultar tipos de nodos y parámetros | GET; permite verificar un nodo |
Las rutas pueden cambiar entre versiones. Consulta la documentación actual o server.py si el comportamiento difiere.
Tipos de mensajes WebSocket
Al esperar una tarea, observa estos mensajes:
status: estado de la cola, incluidoqueue_remainingexecution_start: inicio conprompt_idexecution_cached: nodos reutilizados desde cachéexecuting: nodo actual;node is Nonecon elprompt_idcorrecto indica finalizaciónprogress: pasos actual y totalexecuted: nodo finalizado con metadata de salida
Para detectar el final, recibe type == "executing", comprueba que data.node sea None y que data.prompt_id coincida con la tarea enviada.
Exportar un workflow en API format
Pasos de Export Workflow (API)
El JSON guardado por la interfaz no es la representación que espera la API. Un workflow normal puede fallar con node_errors, por lo que primero debes exportar el API format.
Sigue estos pasos:
- Carga en ComfyUI un workflow que ya genere una imagen válida
- Elige
File -> Export Workflow (API); algunas versiones muestranSave (API Format) - Guarda un
.json, por ejemploworkflow_api.json - Comprueba ID numéricos como
"3"y"6", además declass_typeeinputsen cada nodo
El nombre del menú puede cambiar; usa la función equivalente para exportar a API de tu versión.
API format frente a Save format
El Save format incluye datos de diseño de la interfaz. El API format conserva solo lo necesario para ejecutar:
| Formato | Contenido | Uso |
|---|---|---|
| Save format | Posiciones, colores, grupos, tamaños y enlaces visuales | Reabrir y editar el diseño en la interfaz |
| API format | ID numéricos, class_type, inputs y _meta opcional | Enviar el workflow mediante script o API |
Enviar un Save format a /prompt puede devolver node_errors o error. Cárgalo en la interfaz y vuelve a exportarlo.
Gestionar los ID de nodos
La parametrización necesita los ID de prompt, seed, dimensiones y salida. Localiza:
- Nodo de prompt:
CLIPTextEncode, a menudo"6"en ejemplos simples - Nodo de seed:
KSampler, a menudo"3" - Nodo de width/height: una entrada de
EmptyLatentImageu otro nodo específico - Nodo de salida:
SaveImageoSaveImageWebsocket
Notes y Groups pueden documentar la función, pero el script debe leer el JSON exportado. Los ID pertenecen al grafo concreto y pueden cambiar al volver a exportar.
Script mínimo: enviar, esperar y descargar
El flujo tiene tres pasos: enviar a /prompt, esperar y leer metadata en /history para descargar mediante /view.
Envío HTTP sin espera
El cliente mínimo hace POST a /prompt y no espera. Un worker separado puede consultar las tareas.
import json
import requests
# Load an API-format workflow
with open("workflow_api.json", "r") as f:
workflow = json.load(f)
# Build the request payload
payload = {
"prompt": workflow,
"client_id": "my-script-client" # Optional; associates the job with WebSocket events
}
# Add the job to the queue
response = requests.post("http://127.0.0.1:8188/prompt", json=payload)
if response.status_code == 200:
result = response.json()
prompt_id = result["prompt_id"]
print(f"Submitted: {prompt_id}")
else:
error = response.json()
print(f"Submission failed: {error}")
# node_errors contains node-level validation details
Una respuesta correcta incluye {"prompt_id": "...", "number": ...}; un error incluye {"error": {...}, "node_errors": {...}}. El envío solo añade la tarea a la cola.
Esperar por WebSocket
Los eventos WebSocket evitan un polling agresivo. Conéctate, envía la tarea y espera el evento terminal executing.
import json
import uuid
import requests
import websocket
# Load the workflow
with open("workflow_api.json", "r") as f:
workflow = json.load(f)
# Generate a client ID
client_id = str(uuid.uuid4())
# Connect to WebSocket
ws = websocket.create_connection(f"ws://127.0.0.1:8188/ws?clientId={client_id}")
# Submit the job
payload = {"prompt": workflow, "client_id": client_id}
response = requests.post("http://127.0.0.1:8188/prompt", json=payload)
prompt_id = response.json()["prompt_id"]
# Wait for completion
while True:
message = ws.recv()
data = json.loads(message)
if data["type"] == "executing":
# node is None with the same prompt_id when the job is complete
if data["data"]["node"] is None and data["data"]["prompt_id"] == prompt_id:
print("Job complete")
break
ws.close()
Añade una espera máxima, por ejemplo 300 segundos. /interrupt detiene la ejecución actual, así que úsalo con cuidado.
Descargar con History y View
Tras finalizar, solicita /history/{prompt_id} para obtener metadata y descarga cada archivo mediante /view.
import requests
# Retrieve job history
history_url = f"http://127.0.0.1:8188/history/{prompt_id}"
history = requests.get(history_url).json()
# Traverse output nodes
outputs = history[prompt_id]["outputs"]
for node_id, node_output in outputs.items():
if "images" in node_output:
for image in node_output["images"]:
filename = image["filename"]
subfolder = image.get("subfolder", "")
type = image.get("type", "output")
# Build the download URL
view_url = f"http://127.0.0.1:8188/view?filename={filename}&subfolder={subfolder}&type={type}"
# Download the binary image
img_data = requests.get(view_url).content
with open(f"output_{filename}", "wb") as f:
f.write(img_data)
print(f"Saved: output_{filename}")
outputs sigue {node_id: {"images": [{"filename": "...", "subfolder": "...", "type": "..."}]}}. History entrega metadata y /view el binario.
Parametrizar tareas por lotes
Un workflow estable se convierte en plantilla. Cada iteración cambia solo prompt, seed, dimensiones u otra entrada elegida.
Parametrizar prompt, seed y dimensiones
Modifica los valores bajo inputs:
import json
import random
# Load the workflow
with open("workflow_api.json", "r") as f:
workflow = json.load(f)
# Change the prompt; use the IDs from your own workflow
workflow["6"]["inputs"]["text"] = "a beautiful landscape, sunset, mountains"
# Generate a random seed
workflow["3"]["inputs"]["seed"] = random.randint(0, 1000000)
# Change the dimensions
workflow["5"]["inputs"]["width"] = 1024
workflow["5"]["inputs"]["height"] = 768
# Submit the job...
Confirma los ID "6", "3" y "5" en tu exportación. Después, un bucle puede cambiar prompt y seed antes de cada envío:
prompts = [
"product photo, white background",
"product photo, outdoor scene",
"product photo, studio lighting"
]
for i, prompt_text in enumerate(prompts):
workflow["6"]["inputs"]["text"] = prompt_text
workflow["3"]["inputs"]["seed"] = random.randint(0, 1000000)
# Submit the job
payload = {"prompt": workflow, "client_id": client_id}
response = requests.post("http://127.0.0.1:8188/prompt", json=payload)
prompt_id = response.json()["prompt_id"]
# Wait over WebSocket...
# Download outputs...
Los ejemplos oficiales también modifican KSampler.seed y CLIPTextEncode.text, útiles para localizar parámetros.
Estrategias de cola para lotes
Para 200 imágenes puedes aumentar batch size o enviar muchos prompts. La opción segura depende del modelo y del margen de VRAM medido.
| Estrategia | Características | Casos adecuados |
|---|---|---|
| Una imagen por envío | VRAM más fácil de controlar por tarea | Poco margen, modelos grandes o workflows complejos |
| Batch size | Varias imágenes en un prompt y, normalmente, mayor pico de VRAM | Margen medido, modelos pequeños y workflows simples |
| Solicitudes concurrentes | Varios prompts con número activo limitado | Capacidad medida y workers controlados |
Enviar 20 prompts a la vez puede saturar la cola y causar OOM o un cierre del servicio.
El inicio conservador combina envío secuencial, finalización WebSocket y monitorización de VRAM. Envía la siguiente tarea después de la anterior. Si falta memoria, reduce la carga o prueba opciones actuales.
Ejemplo de control de concurrencia
En un servidor con varias GPU o workers aislados, un semáforo limita las tareas activas:
import copy
import threading
# Allow at most two active jobs
semaphore = threading.Semaphore(2)
def submit_and_wait(prompt_text, seed):
with semaphore:
# Give each task its own copy instead of mutating shared state
job_workflow = copy.deepcopy(workflow)
job_workflow["6"]["inputs"]["text"] = prompt_text
job_workflow["3"]["inputs"]["seed"] = seed
# Submit and wait...
# WebSocket loop...
# The permit is returned for the next task
# Submit the batch
threads = []
for i in range(50):
t = threading.Thread(target=submit_and_wait, args=(prompts[i], seeds[i]))
threads.append(t)
t.start()
for t in threads:
t.join()
Empieza con concurrencia 1. Aumenta solo después de medir pico, latencia y fallos con el workflow real. Precisión, VAE, posprocesado y aislamiento cambian el límite.
Monitorizar la VRAM
Durante el lote, consulta estadísticas y pausa los nuevos envíos por encima de un umbral:
import time
def check_vram(threshold=0.8):
stats = requests.get("http://127.0.0.1:8188/system_stats").json()
vram_used = stats["system_stats"]["devices"][0]["vram_used"]
vram_total = stats["system_stats"]["devices"][0]["vram_total"]
return (vram_used / vram_total) > threshold
# Check memory before each submission
for prompt_text in prompts:
while check_vram(0.85):
print("VRAM pressure is high; waiting 30 seconds...")
time.sleep(30)
# Submit the next job...
# Wait over WebSocket...
El pico suele aparecer durante KSampler y puede bajar al terminar. Un intervalo de 10 a 30 segundos evita saturar el servicio.
Archivar las salidas
Las salidas necesitan una estructura predecible. {prompt_id}_{seed}_{timestamp}.png conserva ID, seed y hora.
Registra como mínimo:
prompt_id: ID de tarea de ComfyUIseed: seed aleatorioprompt_text: prompt usadowidth/height: dimensionestimestamp: hora de generaciónbusiness_id: identificador de negocio como SKU o pedido
Organiza por fecha o lote, por ejemplo outputs/20260624/batch_001/. SQLite o PostgreSQL puede relacionar metadata y rutas.
Lista de ingeniería backend
Un backend necesita request ID, timeouts, límites de cola, monitorización de memoria y tratamiento explícito de errores. Un script funcional no basta para producción.
Request ID e idempotencia
Genera un request_id único, como UUID o pedido, y relaciónalo con prompt_id. Si vuelve un ID ya terminado, devuelve el resultado existente.
import uuid
# Business request ID
request_id = str(uuid.uuid4())
# Store the mapping in a database or cache
request_prompt_map[request_id] = prompt_id
# Return an existing result for duplicate requests
if request_id in completed_requests:
return get_cached_result(request_id)
ComfyUI crea prompt_id, distinto del request_id de la aplicación. Guarda la relación.
Timeouts y cancelación
Define una duración máxima, por ejemplo 300 segundos. Al superarla, /interrupt detiene la ejecución actual.
import time
timeout = 300 # Five minutes
start_time = time.time()
# Wait for WebSocket messages...
while True:
elapsed = time.time() - start_time
if elapsed > timeout:
# Interrupt the current execution
requests.post("http://127.0.0.1:8188/interrupt")
print("Job timed out and was interrupted")
break
# Process normal messages...
Si WebSocket se desconecta o queda en silencio, vuelve a conectar o consulta history. Tras interrumpir, revisa /queue y decide sobre las tareas pendientes.
Límites de cola y monitorización de VRAM
Establece un máximo explícito, como cinco tareas pendientes, y rechaza o aplaza el exceso. Limita también los workers activos.
Consulta /system_stats para revisar la VRAM:
stats = requests.get("http://127.0.0.1:8188/system_stats").json()
vram_used = stats["system_stats"]["devices"][0]["vram_used"]
vram_total = stats["system_stats"]["devices"][0]["vram_total"]
vram_percent = vram_used / vram_total
if vram_percent > 0.8:
print("VRAM pressure is high; rejecting a new request")
Con presión alta, rechaza trabajo nuevo o espera a que baje la cola. Reduce batch size y complejidad antes de repetir.
Clasificación de errores y reintentos
Cada fallo requiere una respuesta distinta:
| Error | Causa probable | Tratamiento |
|---|---|---|
node_errors | Modelo/nodo ausente, parámetro inválido o entrada faltante | Corregir workflow y entorno; no reintentar |
| OOM | VRAM insuficiente | Reducir batch/carga y cambiar una opción verificada; no repetir igual |
| Desconexión WebSocket | Problema de red | Reconectar y consultar /history/{prompt_id} |
| Timeout de tarea | Carga lenta o workflow complejo | Aumentar timeout o simplificar; reintentar una vez |
| Cierre del servicio | Memoria agotada o fallo de GPU | Revisar logs, reiniciar y reintentar con cautela |
node_errors identifica clases ausentes y tipos incompatibles. Corrige workflow, custom nodes o modelos. Un OOM también exige cambiar la carga.
Comparar API Cloud y local
Comfy Cloud ejecuta workflows alojados, pero autenticación, estado, WebSocket y concurrencia difieren. El API format se reutiliza; los endpoints deben seguir la referencia actual.
Diferencias de rutas
Las diferencias principales son:
| Función | API local | API Cloud |
|---|---|---|
| Enviar tarea | /prompt | /api/prompt |
| Estado/resultados | /history/{prompt_id} | /api/job/{prompt_id}/status y /api/jobs/{job_id} |
| Descargar salida | /view | /api/view |
| WebSocket | /ws?clientId=... | /ws?clientId=...&token=... |
Cloud requiere X-API-Key y una suscripción. La API local solo escucha 127.0.0.1 por defecto; con --listen, proxy o redirección debes añadir autenticación y control de acceso. Cloud sigue siendo experimental. /api/history_v2/{prompt_id} está deprecated en favor de /api/jobs/{job_id}.
Concurrencia y límites de suscripción
La concurrencia Cloud depende del plan; el exceso espera en cola. Las salidas viven en cloud storage y /api/view devuelve una URL firmada temporal.
Planes, límites, tiempo y precio pueden cambiar, por lo que no se fijan cifras. Cloud evita gestionar la GPU local; en local administras cola, VRAM, seguridad y estabilidad.
Próximos pasos
Dentro de la serie
Esta página lleva de un workflow funcional a una producción por lotes programable:
- Reutilizar workflows de ComfyUI: importación, nodos ausentes y rutas de modelos
- Optimizar ComfyUI con poca VRAM: memoria, batch, OOM y Tiled VAE
- Vídeo con ComfyUI: límite entre lotes de imágenes y workflows de vídeo
- Mantenimiento de ComfyUI: nodos ausentes, inicio y conflictos de versiones
Temas relacionados
Para patrones más amplios de automatización:
- Crear workflows de IA con n8n: conectar ComfyUI con varias herramientas
- API de Ollama: llamadas programáticas, colas y salida estructurada
- Salida LLM estructurada: extracción fiable e integración API
Conclusión
El paso a producción con scripts consiste en exportar el workflow API, esperar por WebSocket, descargar con /history y /view, parametrizar entradas y añadir request ID, timeout, límites y monitorización VRAM.
Empieza con un workflow y una imagen. Genera después diez imágenes parametrizadas y observa memoria y cola. Añade idempotencia, cancelación, monitorización y registros cuando el lote sea estable.
Ejecutar el primer lote con la API de ComfyUI
Valida el recorrido local desde la exportación hasta el archivo de resultados.
- 1
Step 1: Validar el workflow GUI
Genera una imagen fiable y revisa modelos, custom nodes, entradas y nodos de salida. - 2
Step 2: Exportar API format
Usa Export Workflow (API) y comprueba class_type e inputs en cada nodo. - 3
Step 3: Enviar una tarea
Haz POST del workflow a /prompt, conserva prompt_id y revisa node_errors si falla. - 4
Step 4: Esperar y descargar
Espera por /ws o consulta /history/{prompt_id}; descarga cada salida mediante /view. - 5
Step 5: Parametrizar entradas
Copia la plantilla, cambia prompt, seed, width, height y filename_prefix y valida node ID y class_type. - 6
Step 6: Añadir protecciones
Empieza en serie y añade job_id, idempotencia, límite de cola, timeout, VRAM, errores y archivo.
FAQ
¿Qué workflow JSON usa la API de ComfyUI?
¿Cuál es el flujo mínimo de la API local?
¿Qué significa node_errors?
¿Puedo recuperar el resultado si WebSocket se corta?
¿Conviene aumentar batch size o enviar varios prompts?
¿Se pueden enviar varios prompts en paralelo?
¿Comfy Cloud API y la API local son iguales?
12 min de lectura · Publicado el: 24 jul 2026 · Actualizado el: 24 jul 2026
Guía práctica de ComfyUI y Stable Diffusion
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Crear videos en ComfyUI con Wan y AnimateDiff
Configura Wan o AnimateDiff en ComfyUI, organiza modelos y custom nodes, y resuelve frames, FPS, límites de VRAM, exportación de video y errores habituales del workflow.
Parte 9 de 10
Siguiente
Este es el artículo más reciente de la serie por ahora.



Comentarios
Inicia sesión con GitHub para dejar un comentario