Automatiser des séries d’images avec l’API ComfyUI

"La documentation officielle ComfyUI décrit la validation et la mise en file via /prompt, ainsi que /ws, /history, /view, /queue et /interrupt."
Vous disposez d’un workflow ComfyUI stable et devez produire quatre visuels principaux pour chacun de 200 produits. Modifier manuellement le prompt et le seed à chaque exécution n’est pas viable. Le premier POST vers /prompt renvoie pourtant node_errors, car le fichier exporté n’est pas au format API. Une fois le traitement terminé, /history ne contient qu’un filename et un subfolder : le fichier réel doit être récupéré via /view.
Le passage de la GUI à une production scriptée repose sur trois points : exporter le bon workflow au format API, attendre la fin via WebSocket plutôt que par polling aveugle, puis limiter la concurrence et la VRAM pour éviter les OOM. La suite fournit un script minimal, des exemples paramétrés, des stratégies de file d’attente et une checklist backend.
Accéder à l’API locale de ComfyUI Server
Démarrage CLI et port par défaut
Le service local ComfyUI écoute par défaut sur 127.0.0.1:8188. Un démarrage normal suffit pour les processus de la même machine. N’indiquez une adresse d’écoute que pour un accès depuis le réseau local :
# 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
Après Starting server, ouvrez http://127.0.0.1:8188. L’affichage de l’interface ComfyUI confirme que le service est prêt.
Lorsque la VRAM manque, testez selon la version --lowvram, --novram ou, en dernier recours très lent, --cpu. Avec une marge importante, --highvram peut aussi être évalué. N’associez pas mécaniquement ces options à une capacité fixe : modèle, précision, VAE et structure du workflow modifient le pic. Vérifiez python main.py --help et la documentation officielle actuelle.
Principales routes API
Les routes locales principales sont définies dans server.py. Un script utilise généralement les endpoints suivants :
| Route | Rôle | Paramètres/réponse |
|---|---|---|
/prompt | Valider un workflow et l’ajouter à la file | POST {"prompt": workflow_dict, "client_id": "..."} ; renvoie prompt_id en cas de succès et node_errors en cas d’échec |
/history/{prompt_id} | Obtenir l’historique et les métadonnées | GET ; outputs contient filename, subfolder et type |
/view?filename=...&subfolder=...&type=... | Télécharger un fichier de sortie | GET ; renvoie les données binaires |
/ws | Recevoir l’état via WebSocket | ws://127.0.0.1:8188/ws?clientId=... |
/queue | Consulter la file actuelle | GET ; renvoie les tâches en attente |
/interrupt | Interrompre l’exécution actuelle | POST ; utile pour les timeouts |
/upload/image | Envoyer une image d’entrée | POST multipart/form-data |
/object_info | Interroger les types de nœuds et paramètres | GET ; permet de vérifier un nœud |
Les routes peuvent évoluer entre versions. Consultez la documentation actuelle ou server.py si le comportement diffère.
Types de messages WebSocket
Surveillez les messages suivants pendant l’attente :
status: état de la file, dontqueue_remainingexecution_start: début de l’exécution avecprompt_idexecution_cached: nœuds réutilisés depuis le cacheexecuting: nœud courant ;node is Noneavec le bonprompt_idindique la finprogress: étape actuelle et totalexecuted: nœud terminé avec ses métadonnées
Pour détecter la fin, recevez un message de type "executing", vérifiez que data.node vaut None et que data.prompt_id correspond à la tâche envoyée.
Exporter un workflow au format API
Procédure Export Workflow (API)
Le JSON enregistré par l’interface n’est pas la représentation attendue par l’API. Un workflow ordinaire peut échouer avec node_errors ; exportez d’abord le format API.
Procédez ainsi :
- Chargez un workflow qui génère déjà une image valide dans ComfyUI
- Choisissez
File -> Export Workflow (API); certaines versions affichentSave (API Format) - Enregistrez un fichier
.json, par exempleworkflow_api.json - Vérifiez les ID numériques comme
"3"et"6", ainsi queclass_typeetinputsdans chaque nœud
Le libellé peut changer ; utilisez la commande d’export API équivalente de votre version.
API format et Save format
Le Save format contient la mise en page de l’interface. L’API format conserve uniquement les informations d’exécution :
| Format | Contenu | Usage |
|---|---|---|
| Save format | Positions, couleurs, groupes, dimensions et liens visuels | Réouvrir et modifier la mise en page |
| API format | ID numériques, class_type, inputs et _meta facultatif | Envoyer le workflow par script ou API |
Un Save-format envoyé à /prompt peut renvoyer node_errors ou error. Chargez-le dans l’interface et réexportez-le en API format.
Gérer les ID de nœuds
La paramétrisation nécessite les ID du prompt, du seed, des dimensions et de la sortie. Repérez :
- Nœud prompt :
CLIPTextEncode, souvent"6"dans les exemples simples - Nœud seed :
KSampler, souvent"3" - Nœud width/height : entrée de
EmptyLatentImageou d’un nœud propre au workflow - Nœud de sortie :
SaveImageouSaveImageWebsocket
Les Notes ou Groups peuvent documenter les rôles, mais le script doit toujours lire le JSON exporté. Les ID appartiennent au graphe et peuvent changer après réexport.
Script minimal : envoyer, attendre et télécharger
Le flux comporte trois étapes : envoyer le workflow à /prompt, attendre la fin, puis lire les métadonnées via /history et télécharger via /view.
Envoi HTTP sans attente
Le client minimal POSTe le workflow à /prompt sans attendre. Un worker séparé peut suivre les tâches.
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
Le succès contient {"prompt_id": "...", "number": ...} ; l’échec contient {"error": {...}, "node_errors": {...}}. L’envoi ajoute seulement la tâche à la file.
Attendre via WebSocket
WebSocket évite un polling agressif. Connectez-vous, envoyez la tâche, puis attendez l’événement 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()
Ajoutez une attente maximale, par exemple 300 secondes. /interrupt arrête l’exécution actuelle et doit être utilisé prudemment.
Télécharger avec History et View
Après la fin, récupérez les métadonnées avec /history/{prompt_id}, puis chaque fichier avec /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 suit {node_id: {"images": [{"filename": "...", "subfolder": "...", "type": "..."}]}}. History donne les métadonnées ; /view renvoie le binaire.
Paramétrer les tâches en série
Un workflow stable devient un modèle de série. Chaque itération change seulement le prompt, le seed, les dimensions ou une autre entrée choisie.
Paramétrer prompt, seed et dimensions
Modifiez les valeurs sous 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...
Confirmez les ID "6", "3" et "5" dans votre export. Une boucle peut ensuite modifier prompt et seed avant chaque envoi :
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...
Les exemples officiels modifient aussi KSampler.seed et CLIPTextEncode.text, utiles pour repérer les paramètres.
Stratégies de file pour les séries
Pour 200 images, vous pouvez augmenter batch size ou envoyer plusieurs prompts. Le choix dépend du modèle et de la marge VRAM mesurée.
| Stratégie | Caractéristiques | Cas adaptés |
|---|---|---|
| Une image par envoi | VRAM plus simple à contrôler par tâche | Faible marge, gros modèles ou workflows complexes |
| Batch size | Plusieurs images dans un prompt, pic VRAM généralement supérieur | Marge mesurée, petit modèle, workflow simple |
| Requêtes concurrentes | Plusieurs prompts avec nombre actif limité | Capacité mesurée et workers contrôlés |
Envoyer 20 prompts simultanément peut saturer la file et provoquer OOM ou un crash.
Le départ prudent combine envoi séquentiel, fin WebSocket et suivi VRAM. N’envoyez la tâche suivante qu’après la précédente. Si la mémoire manque, réduisez la charge ou testez les options actuelles.
Exemple de contrôle de concurrence
Sur plusieurs GPU ou workers isolés, un sémaphore limite les tâches actives :
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()
Commencez à concurrence 1. Augmentez après mesure du pic, de la latence et des échecs. Précision, VAE, post-traitement et isolation des workers changent la limite.
Surveiller la VRAM
Pendant la série, interrogez les statistiques et suspendez les nouveaux envois au-dessus d’un seuil :
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...
Le pic apparaît souvent pendant KSampler et peut baisser après. Un contrôle toutes les 10 à 30 secondes suffit.
Archiver les sorties
Les sorties exigent une organisation prévisible. {prompt_id}_{seed}_{timestamp}.png conserve l’ID, le seed et l’heure.
Enregistrez au minimum :
prompt_id: ID de tâche ComfyUIseed: seed aléatoireprompt_text: prompt utiliséwidth/height: dimensionstimestamp: heure de générationbusiness_id: identifiant métier comme SKU ou commande
Classez par date ou lot, par exemple outputs/20260624/batch_001/. SQLite ou PostgreSQL peut relier métadonnées et chemins.
Checklist d’ingénierie backend
Un backend doit ajouter request ID, timeout, limite de file, suivi mémoire et traitement explicite des erreurs. Un script fonctionnel ne suffit pas en production.
Request ID et idempotence
Créez un request_id métier unique et associez-le au prompt_id. Si un ID terminé revient, renvoyez le résultat existant.
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 crée le prompt_id, distinct du request_id applicatif. Persistez cette association.
Timeouts et annulation
Fixez une durée maximale, par exemple 300 secondes. À l’échéance, /interrupt arrête l’exécution actuelle.
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 coupe ou reste muet, reconnectez-vous ou consultez history. Après interruption, vérifiez /queue et décidez du sort des tâches en attente.
Limites de file et suivi VRAM
Fixez une limite explicite, par exemple cinq tâches en attente, et refusez ou reportez le surplus. Limitez aussi les workers actifs.
Interrogez /system_stats pour 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")
Sous pression, refusez les nouvelles tâches ou attendez. Réduisez batch size et complexité avant de répéter.
Classification des erreurs et reprises
Chaque échec demande une réponse adaptée :
| Erreur | Cause probable | Traitement |
|---|---|---|
node_errors | Modèle/nœud absent, paramètre invalide ou entrée manquante | Corriger workflow et environnement ; ne pas relancer |
| OOM | VRAM insuffisante | Réduire batch/charge et modifier une option vérifiée ; ne pas relancer à l’identique |
| Coupure WebSocket | Problème réseau | Reconnecter et consulter /history/{prompt_id} |
| Timeout | Chargement lent ou workflow complexe | Augmenter le timeout ou simplifier ; une seule reprise |
| Crash du service | Mémoire épuisée ou GPU défaillant | Lire les logs, redémarrer et reprendre prudemment |
node_errors identifie classes absentes et types incompatibles. Corrigez workflow, custom nodes ou modèles. Un OOM exige aussi un changement de charge.
Comparer API Cloud et locale
Comfy Cloud exécute les workflows de façon hébergée, mais authentification, état, WebSocket et concurrence diffèrent. Le format API reste réutilisable ; les endpoints doivent suivre la référence actuelle.
Différences de routes
Principales différences :
| Fonction | API locale | API Cloud |
|---|---|---|
| Envoyer une tâche | /prompt | /api/prompt |
| État/résultats | /history/{prompt_id} | /api/job/{prompt_id}/status et /api/jobs/{job_id} |
| Télécharger | /view | /api/view |
| WebSocket | /ws?clientId=... | /ws?clientId=...&token=... |
Cloud requiert X-API-Key et un abonnement. L’API locale écoute seulement 127.0.0.1 par défaut ; avec --listen, proxy ou redirection, vous devez ajouter authentification et contrôle d’accès. L’API Cloud reste experimental. /api/history_v2/{prompt_id} est deprecated au profit de /api/jobs/{job_id}.
Concurrence et limites d’abonnement
La concurrence Cloud dépend de l’abonnement ; le surplus attend. Les sorties sont dans le cloud storage et /api/view renvoie une URL signée temporaire.
Plans, limites, durée et prix évoluent, sans valeurs figées ici. Cloud évite la gestion GPU locale ; en local, vous gérez file, VRAM, sécurité et stabilité.
Étapes suivantes
Dans la série
Cette page fait passer d’un workflow fonctionnel à une production en série programmable :
- Réutiliser des workflows ComfyUI : importer, corriger les nœuds manquants et les chemins de modèles
- Optimisation ComfyUI avec peu de VRAM : options mémoire, batch, OOM et Tiled VAE
- Vidéo ComfyUI : limite entre séries d’images et workflows vidéo
- Maintenance ComfyUI : nœuds absents, démarrage et conflits de versions
Sujets connexes
Pour des schémas d’automatisation plus larges :
- Créer des workflows IA avec n8n : relier ComfyUI à plusieurs outils
- API Ollama : appels programmatiques, files et sorties structurées
- Sorties LLM structurées : extraction fiable et intégration API
Conclusion
Le passage à la production scriptée consiste à exporter le workflow API, attendre via WebSocket, récupérer via /history et /view, paramétrer les entrées puis ajouter request ID, timeout, limites et suivi VRAM.
Commencez par un workflow et une image. Générez ensuite dix images paramétrées et observez mémoire et file. Ajoutez idempotence, annulation, suivi et journal après stabilisation.
Exécuter une première série avec l’API ComfyUI
Validez le parcours local, de l’export du workflow à l’archivage des résultats.
- 1
Step 1: Valider le workflow GUI
Générez une image fiable et vérifiez modèles, custom nodes, fichiers d’entrée et nœuds de sortie. - 2
Step 2: Exporter l’API format
Utilisez Export Workflow (API) et vérifiez class_type et inputs dans chaque nœud. - 3
Step 3: Envoyer une tâche
POSTez le workflow dans prompt vers /prompt, conservez prompt_id et lisez node_errors en cas d’échec. - 4
Step 4: Attendre et télécharger
Attendez l’événement via /ws ou interrogez /history/{prompt_id}, puis récupérez chaque sortie via /view. - 5
Step 5: Paramétrer quelques entrées
Copiez le modèle, modifiez prompt, seed, width, height et filename_prefix, puis validez node ID et class_type. - 6
Step 6: Ajouter les protections
Démarrez séquentiellement, puis ajoutez job_id, idempotence, limite de file, timeout, suivi VRAM, erreurs et archivage.
FAQ
Quel workflow JSON utiliser avec l’API ComfyUI ?
Quel est le flux minimal de l’API locale ?
Que signifie node_errors ?
Peut-on récupérer le résultat après une coupure WebSocket ?
Faut-il augmenter batch size ou envoyer plusieurs prompts ?
Peut-on envoyer plusieurs prompts en parallèle ?
Comfy Cloud API et API locale sont-elles identiques ?
12 min de lecture · Publié le: 24 juil. 2026 · Mis à jour le: 24 juil. 2026
Guide pratique ComfyUI et Stable Diffusion
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Créer des vidéos ComfyUI avec Wan et AnimateDiff
Configurez Wan ou AnimateDiff dans ComfyUI, placez modèles et custom nodes, puis réglez frames, FPS, VRAM, export vidéo et erreurs courantes du workflow.
Partie 9 sur 10
Suivant
C’est le dernier article publié dans cette série pour le moment.



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire