Cambia tema

Automatizzare la generazione di immagini con l’API ComfyUI

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

"La documentazione ufficiale descrive validazione e coda tramite /prompt, oltre a /ws, /history, /view, /queue e /interrupt."

Hai già un workflow ComfyUI stabile e devi creare quattro immagini principali per ciascuno di 200 prodotti. Modificare prompt e seed a mano non è sostenibile. Il primo POST a /prompt restituisce node_errors perché il file non è in API format. Al termine, /history mostra solo filename e subfolder; il file reale va scaricato tramite /view.

Il passaggio dalla GUI alla produzione via script richiede il corretto API format, l’attesa WebSocket senza polling cieco e il controllo di concorrenza e VRAM per evitare OOM. Seguono uno script minimo, parametri, strategie di coda e una checklist backend.

Accesso all’API locale di ComfyUI Server

Avvio da CLI e porta predefinita

ComfyUI ascolta su 127.0.0.1:8188 per impostazione predefinita. L’avvio normale basta ai processi locali. Specifica un indirizzo di ascolto solo per un dispositivo nella LAN:

# 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

Quando il terminale mostra Starting server, apri http://127.0.0.1:8188. Se compare l’interfaccia ComfyUI, il servizio è pronto.

Se la VRAM è poca, prova --lowvram, --novram o, come ultima soluzione lenta, --cpu, se supportati. Con margine ampio valuta --highvram. Non associare queste opzioni a una capacità fissa: modello, precisione, VAE e workflow cambiano il picco. Controlla python main.py --help e la documentazione ufficiale corrente.

Route principali dell’API

Le route locali sono definite in server.py. Uno script usa soprattutto questi endpoint:

RouteScopoParametri/risposta
/promptValidare il workflow e inserirlo in codaPOST {"prompt": workflow_dict, "client_id": "..."}; restituisce prompt_id o node_errors
/history/{prompt_id}Ottenere cronologia e metadataGET; outputs include filename, subfolder e type
/view?filename=...&subfolder=...&type=...Scaricare un fileGET; restituisce dati binari
/wsRicevere lo stato via WebSocketws://127.0.0.1:8188/ws?clientId=...
/queueConsultare la codaGET; restituisce i job in attesa
/interruptInterrompere l’esecuzione correntePOST; utile per timeout
/upload/imageCaricare un’immagine di inputPOST multipart/form-data
/object_infoConsultare tipi di nodi e parametriGET; verifica la presenza di un nodo

Le route possono cambiare tra versioni. Consulta la documentazione corrente o server.py se il comportamento differisce.

Tipi di messaggi WebSocket

Durante l’attesa controlla:

  • status: stato della coda, incluso queue_remaining
  • execution_start: avvio con prompt_id
  • execution_cached: nodi riutilizzati dalla cache
  • executing: nodo corrente; node is None con il prompt_id corretto indica la fine
  • progress: passaggio corrente e totale
  • executed: nodo completato con metadata di output

Per rilevare la fine, ricevi type == "executing", verifica che data.node sia None e che data.prompt_id coincida con il job inviato.

Esportare un workflow in API format

Passaggi di Export Workflow (API)

Il JSON salvato dall’interfaccia non è il formato atteso dall’API. Un workflow normale può fallire con node_errors, quindi esporta prima l’API format.

Procedi così:

  1. Carica in ComfyUI un workflow che genera già un’immagine valida
  2. Scegli File -> Export Workflow (API); alcune versioni mostrano Save (API Format)
  3. Salva un file .json, per esempio workflow_api.json
  4. Verifica ID numerici come "3" e "6", più class_type e inputs in ogni nodo

Il nome del menu può cambiare; usa la funzione equivalente per l’esportazione API della tua versione.

API format e Save format

Il Save format include il layout dell’interfaccia. L’API format mantiene soltanto i dati necessari all’esecuzione:

FormatoContenutoUso
Save formatPosizioni, colori, gruppi, dimensioni e collegamenti visiviRiaprire e modificare il layout
API formatID numerici, class_type, inputs e _meta opzionaleInviare il workflow via script o API

Inviare Save format a /prompt può restituire node_errors o error. Caricalo nell’interfaccia ed esportalo di nuovo.

Gestire gli ID dei nodi

La parametrizzazione richiede gli ID di prompt, seed, dimensioni e output. Individua:

  • Nodo prompt: CLIPTextEncode, spesso "6" negli esempi semplici
  • Nodo seed: KSampler, spesso "3"
  • Nodo width/height: input di EmptyLatentImage o di un nodo specifico
  • Nodo di output: SaveImage o SaveImageWebsocket

Note e Group possono documentare il ruolo, ma lo script deve leggere il JSON esportato. Gli ID appartengono al grafo e possono cambiare dopo una nuova esportazione.

Script minimo: invio, attesa e download

Il flusso ha tre passaggi: inviare a /prompt, attendere, leggere i metadata in /history e scaricare tramite /view.

Invio HTTP senza attesa

Il client minimo esegue POST su /prompt e non attende. Un worker separato può interrogare i job.

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 risposta valida contiene {"prompt_id": "...", "number": ...}; un errore contiene {"error": {...}, "node_errors": {...}}. L’invio aggiunge soltanto il job alla coda.

Attendere via WebSocket

Gli eventi WebSocket evitano polling aggressivo. Connettiti, invia il job e attendi l’evento terminale 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()

Aggiungi un’attesa massima, per esempio 300 secondi. /interrupt ferma l’esecuzione corrente, quindi usalo con cautela.

Scaricare con History e View

Dopo il completamento, richiedi /history/{prompt_id} per i metadata e scarica ogni file tramite /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 segue {node_id: {"images": [{"filename": "...", "subfolder": "...", "type": "..."}]}}. History fornisce i metadata; /view il binario.

Parametrizzare i job in serie

Un workflow stabile diventa un modello per la serie. Ogni iterazione modifica solo prompt, seed, dimensioni o un altro input scelto.

Parametrizzare prompt, seed e dimensioni

Modifica i valori in 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...

Conferma gli ID "6", "3" e "5" nel file esportato. Un ciclo può quindi modificare prompt e seed prima di ogni invio:

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...

Gli esempi ufficiali modificano anche KSampler.seed e CLIPTextEncode.text, utili per localizzare i parametri.

Strategie di coda per le serie

Per 200 immagini puoi aumentare batch size o inviare molti prompt. La scelta sicura dipende dal modello e dal margine VRAM misurato.

StrategiaCaratteristicheCasi adatti
Un’immagine per invioVRAM più semplice da controllare per jobPoco margine, modelli grandi o workflow complessi
Batch sizePiù immagini in un prompt e picco VRAM di solito maggioreMargine misurato, modelli piccoli e workflow semplici
Richieste concorrentiPiù prompt con numero attivo limitatoCapacità misurata e worker controllati

Inviare 20 prompt insieme può riempire la coda e causare OOM o arresto del servizio.

L’inizio prudente usa invio sequenziale, completamento WebSocket e monitoraggio VRAM. Invia il job successivo dopo il precedente. Se manca memoria, riduci il carico o prova le opzioni correnti.

Esempio di controllo della concorrenza

Su più GPU o worker isolati, un semaforo limita i job attivi:

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()

Inizia con concorrenza 1. Aumenta dopo aver misurato picco, latenza ed errori nel workflow reale. Precisione, VAE, post-processing e isolamento cambiano il limite.

Monitorare la VRAM

Durante la serie, interroga le statistiche e sospendi i nuovi invii oltre una soglia:

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...

Il picco si verifica spesso durante KSampler e può scendere al termine. Un controllo ogni 10–30 secondi evita di sovraccaricare il servizio.

Archiviare gli output

Gli output richiedono una struttura prevedibile. {prompt_id}_{seed}_{timestamp}.png conserva ID, seed e orario.

Registra almeno:

  • prompt_id: ID del job ComfyUI
  • seed: seed casuale
  • prompt_text: prompt usato
  • width/height: dimensioni
  • timestamp: ora di generazione
  • business_id: identificatore applicativo come SKU o ordine

Organizza per data o serie, per esempio outputs/20260624/batch_001/. SQLite o PostgreSQL può collegare metadata e percorsi.

Checklist di ingegneria backend

Un backend richiede request ID, timeout, limiti della coda, monitoraggio memoria e gestione esplicita degli errori. Uno script funzionante non basta in produzione.

Request ID e idempotenza

Genera un request_id univoco, come UUID o numero d’ordine, e associalo al prompt_id. Se ritorna un ID già completato, restituisci il risultato esistente.

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 il prompt_id, diverso dal request_id dell’applicazione. Salva l’associazione.

Timeout e annullamento

Imposta una durata massima, per esempio 300 secondi. Al superamento, /interrupt ferma l’esecuzione corrente.

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...

Se WebSocket si disconnette o resta senza risposta, riconnettiti o consulta history. Dopo l’interruzione, controlla /queue e decidi sui job in attesa.

Limiti della coda e monitoraggio VRAM

Imposta un massimo esplicito, come cinque job in attesa, e rifiuta o rinvia l’eccesso. Limita anche i worker attivi.

Interroga /system_stats per controllare 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 pressione elevata, rifiuta nuovo lavoro o attendi che la coda scenda. Riduci batch size e complessità prima di riprovare.

Classificazione degli errori e nuovi tentativi

Ogni errore richiede una risposta diversa:

ErroreCausa probabileGestione
node_errorsModello/nodo assente, parametro non valido o input mancanteCorreggere workflow e ambiente; non riprovare
OOMVRAM insufficienteRidurre batch/carico e cambiare un’opzione verificata; non ripetere uguale
Disconnessione WebSocketProblema di reteRiconnettere e consultare /history/{prompt_id}
Timeout del jobCaricamento lento o workflow complessoAumentare timeout o semplificare; riprovare una volta
Arresto del servizioMemoria esaurita o errore GPUControllare log, riavviare e riprovare con cautela

node_errors identifica classi assenti e tipi incompatibili. Correggi workflow, custom nodes o modelli. Anche OOM richiede una modifica del carico.

Confrontare API Cloud e locale

Comfy Cloud esegue workflow ospitati, ma autenticazione, stato, WebSocket e concorrenza differiscono. L’API format è riutilizzabile; gli endpoint devono seguire il riferimento corrente.

Differenze tra le route

Le differenze principali sono:

FunzioneAPI localeAPI Cloud
Inviare job/prompt/api/prompt
Stato/risultati/history/{prompt_id}/api/job/{prompt_id}/status e /api/jobs/{job_id}
Scaricare output/view/api/view
WebSocket/ws?clientId=.../ws?clientId=...&token=...

Cloud richiede X-API-Key e un abbonamento. L’API locale ascolta solo 127.0.0.1 per impostazione predefinita; con --listen, proxy o port forwarding devi aggiungere autenticazione e controllo accessi. Cloud resta experimental. /api/history_v2/{prompt_id} è deprecated a favore di /api/jobs/{job_id}.

Concorrenza e limiti dell’abbonamento

La concorrenza Cloud dipende dal piano; l’eccesso attende in coda. Gli output sono nel cloud storage e /api/view restituisce un URL firmato temporaneo.

Piani, limiti, durata e prezzi cambiano, quindi non fissiamo numeri. Cloud evita la gestione della GPU locale; in locale gestisci coda, VRAM, sicurezza e stabilità.

Passaggi successivi

Nella serie

Questa pagina porta da un workflow funzionante a una produzione in serie programmabile:

  • Riutilizzare workflow ComfyUI: importazione, nodi mancanti e percorsi dei modelli
  • Ottimizzare ComfyUI con poca VRAM: memoria, batch, OOM e Tiled VAE
  • Video con ComfyUI: confine tra serie di immagini e workflow video
  • Manutenzione ComfyUI: nodi mancanti, avvio e conflitti di versione

Argomenti correlati

Per schemi di automazione più ampi:

  • Creare workflow IA con n8n: collegare ComfyUI a più strumenti
  • API Ollama: chiamate programmatiche, code e output strutturati
  • Output LLM strutturato: estrazione affidabile e integrazione API

Conclusione

Passare alla produzione via script significa esportare il workflow API, attendere via WebSocket, scaricare con /history e /view, parametrizzare gli input e aggiungere request ID, timeout, limiti e monitoraggio VRAM.

Inizia con un workflow e un’immagine. Genera poi dieci immagini parametrizzate e osserva memoria e coda. Aggiungi idempotenza, annullamento, monitoraggio e registri quando la serie è stabile.

Eseguire la prima serie con l’API ComfyUI

Valida il percorso locale dall’esportazione all’archiviazione dei risultati.

  1. 1

    Step 1: Validare il workflow GUI

    Genera un’immagine affidabile e verifica modelli, custom nodes, input e nodi di output.
  2. 2

    Step 2: Esportare API format

    Usa Export Workflow (API) e controlla class_type e inputs in ogni nodo.
  3. 3

    Step 3: Inviare un job

    Esegui POST del workflow su /prompt, conserva prompt_id e controlla node_errors in caso di errore.
  4. 4

    Step 4: Attendere e scaricare

    Attendi su /ws o interroga /history/{prompt_id}, poi scarica ogni output con /view.
  5. 5

    Step 5: Parametrizzare gli input

    Copia il modello, cambia prompt, seed, width, height e filename_prefix e verifica node ID e class_type.
  6. 6

    Step 6: Aggiungere protezioni

    Inizia in sequenza e aggiungi job_id, idempotenza, limite, timeout, VRAM, errori e archivio.

FAQ

Quale workflow JSON usa l’API ComfyUI?
Usa API format. Il Save format contiene il layout della GUI; caricalo in ComfyUI ed esportalo con Export Workflow (API).
Qual è il flusso minimo dell’API locale?
Invia il workflow a /prompt, salva prompt_id, attendi via WebSocket o /history/{prompt_id} e chiama /view con filename, subfolder e type.
Che cosa indica node_errors?
Contiene errori per nodo: modello o nodo assente, tipo errato o input mancante. Correggi workflow o ambiente prima di riprovare.
Posso recuperare il risultato dopo una disconnessione WebSocket?
Sì. Conserva prompt_id, riconnettiti o consulta /history/{prompt_id} e /queue. WebSocket non deve essere l’unico registro.
Conviene aumentare batch size o inviare più prompt?
Su una GPU locale, batch 1 e invii sequenziali sono il punto di partenza più sicuro. La coda facilita errori, tentativi e archivio.
Posso inviare più prompt in parallelo?
Sì, ma inizia con concorrenza 1, copia il workflow per job e aumenta dopo aver misurato VRAM, coda ed errori.
Comfy Cloud API e API locale sono uguali?
No. Cloud richiede X-API-Key e abbonamento; endpoint di stato, WebSocket e risultati differiscono e l’API resta experimental.

12 min di lettura · Pubblicato il: 24 lug 2026 · Aggiornato il: 24 lug 2026

Commenti

Accedi con GitHub per lasciare un commento

Easton BlogEaston Blog