API ComfyUI
API ComfyUI
Pourquoi utiliser l'API ?
L'interface graphique de ComfyUI est idéale pour la création interactive, mais l'API ouvre la porte à l'automatisation et à l'intégration dans vos applications :
- Génération d'images en batch automatisé
- Intégration dans une application web ou mobile
- Pipeline de production automatisé
- Bot Discord/Telegram de génération d'images
- Service SaaS de génération d'images
- Tests et benchmarks automatisés
Architecture de l'API
ComfyUI expose une API REST et une API WebSocket sur le même port (8188 par défaut).
Endpoints principaux
| Méthode | Endpoint | Description |
|---|---|---|
| POST | /prompt |
Envoie un workflow pour exécution |
| GET | /history |
Historique des générations |
| GET | /history/{prompt_id} |
Résultat d'une génération spécifique |
| GET | /view?filename=... |
Récupère une image générée |
| GET | /queue |
État de la file d'attente |
| POST | /queue |
Gère la file (supprimer, interrompre) |
| GET | /object_info |
Liste tous les nœuds disponibles et leurs paramètres |
| POST | /upload/image |
Upload une image d'entrée |
| GET | /system_stats |
Statistiques système (VRAM, GPU) |
WebSocket
L'API WebSocket (ws://127.0.0.1:8188/ws) fournit des mises à jour en temps réel :
- Progression du sampling (pourcentage par step)
- Notifications de complétion
- Prévisualisations intermédiaires
Le format du Prompt API
Le cœur de l'API est le prompt : un objet JSON qui décrit le workflow complet sous forme de graphe de nœuds.
Structure
{
"prompt": {
"1": {
"class_type": "CheckpointLoaderSimple",
"inputs": {
"ckpt_name": "juggernautXL.safetensors"
}
},
"2": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "a beautiful sunset over mountains",
"clip": ["1", 1]
}
},
"3": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "blurry, low quality",
"clip": ["1", 1]
}
},
"4": {
"class_type": "EmptyLatentImage",
"inputs": {
"width": 1024,
"height": 1024,
"batch_size": 1
}
},
"5": {
"class_type": "KSampler",
"inputs": {
"model": ["1", 0],
"positive": ["2", 0],
"negative": ["3", 0],
"latent_image": ["4", 0],
"seed": 42,
"steps": 25,
"cfg": 7.0,
"sampler_name": "dpmpp_2m",
"scheduler": "karras",
"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_output"
}
}
}
}
Comprendre les connexions
Les connexions entre nœuds sont représentées par des tuples ["node_id", output_index] :
["1", 0]→ sortie 0 du nœud 1 (MODEL pour un CheckpointLoader)["1", 1]→ sortie 1 du nœud 1 (CLIP)["1", 2]→ sortie 2 du nœud 1 (VAE)
Récupérer le JSON d'un workflow
Astuce : créez votre workflow dans l'interface graphique, puis :
- Activez Enable Dev Mode dans les paramètres
- Cliquez sur Save (API Format) → génère le JSON au format API
- Utilisez ce JSON comme base pour vos appels API
Intégration Python
Client basique
import json
import urllib.request
import urllib.parse
COMFYUI_URL = "http://127.0.0.1:8188"
def queue_prompt(prompt):
"""Envoie un workflow pour exécution."""
data = json.dumps({"prompt": prompt}).encode('utf-8')
req = urllib.request.Request(
f"{COMFYUI_URL}/prompt",
data=data,
headers={'Content-Type': 'application/json'}
)
response = urllib.request.urlopen(req)
return json.loads(response.read())
def get_history(prompt_id):
"""Récupère le résultat d'une génération."""
response = urllib.request.urlopen(
f"{COMFYUI_URL}/history/{prompt_id}"
)
return json.loads(response.read())
def get_image(filename, subfolder="", folder_type="output"):
"""Télécharge une image générée."""
params = urllib.parse.urlencode({
"filename": filename,
"subfolder": subfolder,
"type": folder_type
})
response = urllib.request.urlopen(
f"{COMFYUI_URL}/view?{params}"
)
return response.read()
Client avec WebSocket (temps réel)
import websocket
import uuid
import json
class ComfyUIClient:
def __init__(self, server_address="127.0.0.1:8188"):
self.server_address = server_address
self.client_id = str(uuid.uuid4())
self.ws = websocket.WebSocket()
self.ws.connect(
f"ws://{server_address}/ws?clientId={self.client_id}"
)
def queue_prompt(self, prompt):
"""Envoie un prompt et retourne le prompt_id."""
payload = {
"prompt": prompt,
"client_id": self.client_id
}
data = json.dumps(payload).encode('utf-8')
req = urllib.request.Request(
f"http://{self.server_address}/prompt",
data=data,
headers={'Content-Type': 'application/json'}
)
response = urllib.request.urlopen(req)
return json.loads(response.read())['prompt_id']
def wait_for_completion(self, prompt_id):
"""Attend la fin de la génération via WebSocket."""
while True:
msg = self.ws.recv()
if isinstance(msg, str):
data = json.loads(msg)
if data['type'] == 'executing':
node = data['data'].get('node')
if node is None:
# Génération terminée
break
elif data['type'] == 'progress':
step = data['data']['value']
total = data['data']['max']
print(f" Progression : {step}/{total}")
def generate(self, prompt):
"""Génère une image et retourne les fichiers résultants."""
prompt_id = self.queue_prompt(prompt)
print(f"Prompt envoyé : {prompt_id}")
self.wait_for_completion(prompt_id)
history = get_history(prompt_id)
outputs = history[prompt_id]['outputs']
images = []
for node_id, node_output in outputs.items():
if 'images' in node_output:
for img_info in node_output['images']:
img_data = get_image(
img_info['filename'],
img_info.get('subfolder', ''),
img_info.get('type', 'output')
)
images.append(img_data)
return images
def close(self):
self.ws.close()
Exemple d'utilisation
client = ComfyUIClient()
# Charger un workflow depuis un fichier JSON
with open("mon_workflow_api.json") as f:
workflow = json.load(f)
# Modifier dynamiquement le prompt
workflow["2"]["inputs"]["text"] = "a cyberpunk city at night, neon lights"
workflow["5"]["inputs"]["seed"] = 12345
# Générer
images = client.generate(workflow)
# Sauvegarder
for i, img_data in enumerate(images):
with open(f"result_{i}.png", "wb") as f:
f.write(img_data)
client.close()
Intégration JavaScript/TypeScript
Client Node.js
import WebSocket from 'ws';
interface ComfyUIPromptResponse {
prompt_id: string;
}
class ComfyUIClient {
private serverUrl: string;
private clientId: string;
constructor(serverAddress = '127.0.0.1:8188') {
this.serverUrl = `http://${serverAddress}`;
this.clientId = crypto.randomUUID();
}
async queuePrompt(prompt: Record<string, unknown>): Promise<string> {
const response = await fetch(`${this.serverUrl}/prompt`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt,
client_id: this.clientId,
}),
});
const data: ComfyUIPromptResponse = await response.json();
return data.prompt_id;
}
async waitForCompletion(promptId: string): Promise<void> {
return new Promise((resolve) => {
const ws = new WebSocket(
`ws://${this.serverUrl.replace('http://', '')}/ws?clientId=${this.clientId}`
);
ws.on('message', (data: string) => {
const msg = JSON.parse(data);
if (msg.type === 'executing' && msg.data.node === null) {
ws.close();
resolve();
}
});
});
}
async getImage(filename: string): Promise<Buffer> {
const params = new URLSearchParams({ filename, type: 'output' });
const response = await fetch(`${this.serverUrl}/view?${params}`);
return Buffer.from(await response.arrayBuffer());
}
}
Upload d'images
Pour les workflows nécessitant une image d'entrée (img2img, ControlNet, IP-Adapter) :
import requests
def upload_image(filepath, filename=None):
"""Upload une image vers ComfyUI."""
if filename is None:
filename = os.path.basename(filepath)
with open(filepath, 'rb') as f:
files = {'image': (filename, f, 'image/png')}
data = {'overwrite': 'true'}
response = requests.post(
f"{COMFYUI_URL}/upload/image",
files=files,
data=data
)
return response.json()
# Utilisation
result = upload_image("photo_reference.png")
# result: {"name": "photo_reference.png", "subfolder": "", "type": "input"}
# Puis dans le workflow, référencer l'image :
workflow["load_image_node"]["inputs"]["image"] = "photo_reference.png"
Déploiement en production
Docker avec API exposée
version: '3'
services:
comfyui:
image: comfyanonymous/comfyui
ports:
- "8188:8188"
volumes:
- ./models:/app/models
- ./output:/app/output
- ./input:/app/input
deploy:
resources:
reservations:
devices:
- capabilities: [gpu]
command: >
python main.py
--listen 0.0.0.0
--port 8188
--enable-cors-header
Arguments utiles pour la production
python main.py \
--listen 0.0.0.0 \ # Écouter sur toutes les interfaces
--port 8188 \ # Port
--enable-cors-header \ # Autoriser les requêtes cross-origin
--preview-method auto \ # Prévisualisations pendant la génération
--max-upload-size 100 \ # Taille max d'upload en Mo
--disable-auto-launch # Ne pas ouvrir le navigateur
Proxy avec Nginx
upstream comfyui {
server 127.0.0.1:8188;
}
server {
listen 443 ssl;
server_name comfyui.example.com;
location / {
proxy_pass http://comfyui;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 300s;
}
}
Sécurité
- Ne jamais exposer ComfyUI directement sur Internet sans authentification
- Ajoutez un reverse proxy avec authentification (Basic Auth, OAuth)
- Limitez le rate limiting pour éviter les abus
- Surveillez l'utilisation GPU/VRAM
- Mettez en place des limites de taille et de résolution
Cas d'usage : Bot Discord
import discord
from comfyui_client import ComfyUIClient
bot = discord.Bot()
comfy = ComfyUIClient()
@bot.slash_command(name="generate")
async def generate(ctx, prompt: str):
await ctx.defer()
# Préparer le workflow
workflow = load_base_workflow()
workflow["2"]["inputs"]["text"] = prompt
workflow["5"]["inputs"]["seed"] = random.randint(0, 2**32)
# Générer
images = comfy.generate(workflow)
# Envoyer le résultat
file = discord.File(
io.BytesIO(images[0]),
filename="generated.png"
)
await ctx.followup.send(
f"Prompt : *{prompt}*",
file=file
)
bot.run(TOKEN)