Changer le thème

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

Easton editorial illustration: large dark node-graph workspace with orange connected nodes

"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 :

RouteRôleParamètres/réponse
/promptValider un workflow et l’ajouter à la filePOST {"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éesGET ; outputs contient filename, subfolder et type
/view?filename=...&subfolder=...&type=...Télécharger un fichier de sortieGET ; renvoie les données binaires
/wsRecevoir l’état via WebSocketws://127.0.0.1:8188/ws?clientId=...
/queueConsulter la file actuelleGET ; renvoie les tâches en attente
/interruptInterrompre l’exécution actuellePOST ; utile pour les timeouts
/upload/imageEnvoyer une image d’entréePOST multipart/form-data
/object_infoInterroger les types de nœuds et paramètresGET ; 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, dont queue_remaining
  • execution_start : début de l’exécution avec prompt_id
  • execution_cached : nœuds réutilisés depuis le cache
  • executing : nœud courant ; node is None avec le bon prompt_id indique la fin
  • progress : étape actuelle et total
  • executed : 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 :

  1. Chargez un workflow qui génère déjà une image valide dans ComfyUI
  2. Choisissez File -> Export Workflow (API) ; certaines versions affichent Save (API Format)
  3. Enregistrez un fichier .json, par exemple workflow_api.json
  4. Vérifiez les ID numériques comme "3" et "6", ainsi que class_type et inputs dans 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 :

FormatContenuUsage
Save formatPositions, couleurs, groupes, dimensions et liens visuelsRéouvrir et modifier la mise en page
API formatID numériques, class_type, inputs et _meta facultatifEnvoyer 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 EmptyLatentImage ou d’un nœud propre au workflow
  • Nœud de sortie : SaveImage ou SaveImageWebsocket

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égieCaractéristiquesCas adaptés
Une image par envoiVRAM plus simple à contrôler par tâcheFaible marge, gros modèles ou workflows complexes
Batch sizePlusieurs images dans un prompt, pic VRAM généralement supérieurMarge mesurée, petit modèle, workflow simple
Requêtes concurrentesPlusieurs 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 ComfyUI
  • seed : seed aléatoire
  • prompt_text : prompt utilisé
  • width/height : dimensions
  • timestamp : heure de génération
  • business_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 :

ErreurCause probableTraitement
node_errorsModèle/nœud absent, paramètre invalide ou entrée manquanteCorriger workflow et environnement ; ne pas relancer
OOMVRAM insuffisanteRéduire batch/charge et modifier une option vérifiée ; ne pas relancer à l’identique
Coupure WebSocketProblème réseauReconnecter et consulter /history/{prompt_id}
TimeoutChargement lent ou workflow complexeAugmenter le timeout ou simplifier ; une seule reprise
Crash du serviceMémoire épuisée ou GPU défaillantLire 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 :

FonctionAPI localeAPI 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. 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. 2

    Step 2: Exporter l’API format

    Utilisez Export Workflow (API) et vérifiez class_type et inputs dans chaque nœud.
  3. 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. 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. 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. 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 ?
Utilisez l’API format. Le Save format contient la mise en page de l’interface ; chargez-le dans ComfyUI et exportez-le avec Export Workflow (API).
Quel est le flux minimal de l’API locale ?
POSTez le workflow à /prompt, gardez prompt_id, attendez via WebSocket ou /history/{prompt_id}, puis appelez /view avec filename, subfolder et type.
Que signifie node_errors ?
Il contient les erreurs de validation par nœud : modèle ou nœud absent, mauvais type ou entrée manquante. Corrigez le workflow ou l’environnement avant de relancer.
Peut-on récupérer le résultat après une coupure WebSocket ?
Oui. Conservez prompt_id, reconnectez-vous ou consultez /history/{prompt_id} et /queue. WebSocket ne doit pas être le seul historique.
Faut-il augmenter batch size ou envoyer plusieurs prompts ?
Sur un GPU local, batch 1 et les envois séquentiels sont le départ le plus sûr. Une file facilite isolation des échecs, reprises et archivage.
Peut-on envoyer plusieurs prompts en parallèle ?
Oui, mais commencez à concurrence 1, copiez le workflow pour chaque tâche et augmentez seulement après mesure de la VRAM, de la file et des erreurs.
Comfy Cloud API et API locale sont-elles identiques ?
Non. Cloud exige X-API-Key et un abonnement, et ses endpoints de statut, WebSocket et résultats diffèrent. L’API Cloud reste expérimentale.

12 min de lecture · Publié le: 24 juil. 2026 · Mis à jour le: 24 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog