HiAPI
Blog

Tutoriale

Come chiamare hailuo-2.3-fast/image-to-video tramite l'API HiAPI: pratica con curl e Python

La prima richiesta funzionante per il livello image-to-video veloce ed economico — dall'ID del modello al download di un MP4

Nov 3, 2026
扁平切面矿物剖面风格的16:9封面图,深青底色配青绿与孔雀石色几何平面。

Cosa può fare per te hailuo-2.3-fast/image-to-video

hailuo-2.3-fast/image-to-video è il livello ottimizzato per la velocità nella linea di prodotti image-to-video di HiAPI. Gli fornisci un'immagine statica e un prompt testuale che descrive il movimento desiderato, e ti restituisce una breve clip video che si anima a partire da quell'immagine. Il "fast" nell'ID del modello è il punto chiave: questo livello scambia parte della fedeltà dei dettagli dei modelli video più pesanti con una latenza inferiore e un costo per render più basso, il che lo rende ideale per l'iterazione e la produzione in batch.

Scegli questo modello quando ti serve una prima versione funzionante di una clip accettabile, non un'immagine "hero": anteprime di storyboard, video loop pensati per i social, materiale di sfondo in movimento, o la fase di bozza in una pipeline in cui il modello pesante si occupa del render finale. Se il tuo output richiede inquadrature lunghe, una regia complessa o una rigorosa coerenza dell'identità tra molti frame, affida questi compiti ai livelli più pesanti — mantenendo la stessa struttura di richiesta, così il passaggio è solo una modifica di configurazione.

Tutto ciò che segue ruota attorno a una richiesta che puoi davvero eseguire. L'obiettivo è far arrivare un MP4 su disco, non uno screenshot di una risposta riuscita.

Panoramica della richiesta

Un render video richiede da pochi secondi a qualche minuto, quindi l'API è asincrona: invii un'attività, ricevi un identificatore dell'attività, poi interroghi l'attività finché non segnala il completamento e ti consegna l'output. Questo significa che ogni integrazione ha due fasi — invio e polling — e solo la fase di invio varia in base al modello.

La richiesta di invio contiene quattro elementi sotto il tuo controllo:

  • Endpoint e metodo — una richiesta POST all'endpoint di generazione video di HiAPI.
  • Header di autenticazione — la tua chiave, nella forma Authorization: Bearer $HIAPI_API_KEY. Metti la chiave in una variabile d'ambiente, mai inline nel codice inviato.
  • ID del modello — la stringa letterale hailuo-2.3-fast/image-to-video. È ciò che seleziona il livello.
  • Input — un prompt che descrive il movimento e un'immagine del primo frame. L'immagine è fornita come URL accessibile pubblicamente o come data URI, a seconda di ciò che il tuo account accetta su quell'endpoint; iniziare da un URL è la cosa più semplice. Il corpo JSON viene inviato con Content-Type: application/json.

Metti questi quattro campi in un unico posto nella tua codebase — un dizionario, un file di configurazione, un builder guidato da variabili d'ambiente — così passare in futuro a un altro modello video HiAPI richiederà una sola riga, non una riscrittura. La risposta è una piccola busta JSON in cui l'identificatore dell'attività è l'unico campo che devi persistere; il resto sono metadati di stato.

La richiesta curl funzionante

Questo è il passo di invio completo. Sostituisci l'URL dell'immagine con un indirizzo accessibile al tuo account ed esporta prima la tua chiave.

export HIAPI_API_KEY="la-tua-chiave-qui"

curl -sS -X POST "https://api.hiapi.ai/v1/video/generations" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "hailuo-2.3-fast/image-to-video",
    "prompt": "slow push in, gentle breeze moving the fabric, film grain",
    "image": "https://example.com/first-frame.jpg"
  }'

Una risposta riuscita è un JSON contenente l'identificatore dell'attività, solitamente nella forma {"id": "task_...", "status": "queued"}. La forma esatta dei campi della busta può cambiare, quindi analizza in modo difensivo: leggi l'ID dell'attività per chiave e non per posizione, e tratta i campi sconosciuti come informativi.

Se stai debuggando, aggiungi -i per vedere la riga di stato HTTP. 401 significa quasi sempre che l'header di autenticazione manca o che la variabile si è espansa in una stringa vuota — esegui echo $HIAPI_API_KEY prima di dare la colpa al corpo della richiesta. 400 di solito indica il corpo della richiesta: URL dell'immagine non raggiungibile, model mancante o nome del campo non corrispondente all'endpoint.

Copia l'ID dell'attività restituito in una variabile della shell, così i passaggi successivi non richiedono di reinserirlo:

TASK_ID=$(curl -sS -X POST "https://api.hiapi.ai/v1/video/generations" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"hailuo-2.3-fast/image-to-video","prompt":"slow push in","image":"https://example.com/first-frame.jpg"}' \
  | python -c 'import sys,json; print(json.load(sys.stdin)["id"])')
echo "$TASK_ID"

La stessa richiesta, scritta in Python

La versione Python mantiene il corpo della richiesta byte per byte equivalente a quello di curl, così quando indaghi su un bug puoi confrontare direttamente il risultato di curl e quello di Python.

import os
import requests

API_BASE = "https://api.hiapi.ai/v1"
MODEL = "hailuo-2.3-fast/image-to-video"

def submit_video(prompt: str, image_url: str) -> str:
    resp = requests.post(
        f"{API_BASE}/video/generations",
        headers={"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"},
        json={
            "model": MODEL,
            "prompt": prompt,
            "image": image_url,
        },
        timeout=60,
    )
    resp.raise_for_status()
    return resp.json()["id"]

Ci sono tre abitudini importanti qui. os.environ[...] (non .get) fallisce in modo esplicito quando la variabile manca, ed è proprio così che vuoi che fallisca. Un timeout esplicito impedisce a un socket bloccato di occupare indefinitamente un worker. E raise_for_status() trasforma un corpo di risposta 4xx in un'eccezione registrabile, invece di far sì che la riga successiva generi silenziosamente un KeyError per l'ID mancante.

Questa funzione restituisce deliberatamente solo l'ID dell'attività. Invio e attesa sono preoccupazioni separate; mescolarle rende il ciclo di polling difficile da testare. Nota che l'ID del modello appare una sola volta, a livello di modulo — è l'unico punto da modificare quando passi a un altro modello video HiAPI.

Eseguire il polling dell'attività finché il video è pronto

Il controllo dello stato è una richiesta GET alla risorsa dell'attività. Un'attività in coda o in esecuzione restituisce uno stato non terminale; un'attività completata restituisce uno stato terminale con l'output allegato.

import time

def wait_for_video(task_id: str, timeout: float = 600.0) -> dict:
    deadline = time.monotonic() + timeout
    delay = 2.0
    url = f"{API_BASE}/video/generations/{task_id}"
    while True:
        r = requests.get(
            url,
            headers={"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"},
            timeout=30,
        )
        r.raise_for_status()
        task = r.json()

        if task.get("status") == "succeeded":
            return task
        if task.get("status") in {"failed", "cancelled"}:
            raise RuntimeError(f"task {task_id} ended: {task}")

        if time.monotonic() >= deadline:
            raise TimeoutError(f"task {task_id} still {task.get('status')} after {timeout}s")

        time.sleep(delay)
        delay = min(delay * 1.5, 15.0)

Questo è un ciclo di retry, non un'attesa fissa. Il backoff parte da due secondi e cresce fino a un tetto di quindici secondi, così i render brevi si completano rapidamente e quelli lunghi non martellano l'endpoint. La scadenza viene controllata prima del sonno, quindi il tempo totale effettivo rimane entro il limite invece di superarlo di un intervallo. Usa time.monotonic() invece di time.time(), così le regolazioni dell'orologio non prolungano il ciclo.

Tratta failed e cancelled in modo diverso da "continua ad aspettare": sono terminali ma non riusciti. Quando accade, registra l'intero oggetto attività — è l'unica diagnostica che ottieni, e riprodurre un fallimento a partire da un solo ID è molto doloroso. Tratta le stringhe di stato sconosciute come non terminali e continua il polling, così i nuovi stati intermedi non mandano in crash il tuo worker.

Scaricare ed elaborare l'output

Un'attività completata contiene uno o più URL di output. Recupera i byte e scrivili dove hai il controllo.

def download_video(task: dict, dest: str) -> str:
    url = task["output"][0]["url"] if isinstance(task.get("output"), list) else task["output"]["url"]
    with requests.get(url, stream=True, timeout=120) as r:
        r.raise_for_status()
        with open(dest, "wb") as f:
            for chunk in r.iter_content(chunk_size=1 << 20):
                f.write(chunk)
    return dest

Scarica in streaming, invece di chiamare .content; un video può essere abbastanza grande che bufferizzarlo interamente in memoria è uno spreco per un worker che gestisce più attività. La forma esatta dell'URL di output può variare leggermente a seconda del livello del modello, quindi verifica se è una lista prima di indicizzarla — il ramo sopra copre entrambi i casi.

Regola importante: tratta l'URL restituito come un handle a breve scadenza, non come archiviazione. Appena un'attività riesce, copia il file nel tuo bucket o nel tuo percorso locale e servilo da lì. Persistere l'URL nel database e recuperarlo giorni dopo è il motivo per cui ottieni 403 e una dashboard vuota. Per l'object storage, lo stesso ciclo di byte in streaming può alimentare direttamente il metodo di upload del tuo client — se il tuo SDK accetta file-like object, non serve un file temporaneo intermedio.

Consigli pratici e trappole comuni

  • Usa l'header di autenticazione, non i parametri di query. La chiave va in Authorization: Bearer .... Una chiave incollata nell'URL è una chiave nei log di accesso.
  • La raggiungibilità dell'immagine conta più della correttezza dell'immagine. Un 400 all'invio è molto più spesso un URL che l'API non riesce a recuperare — localhost, bucket privati, host con hotlink protection, link che scadono — piuttosto che un problema del prompt. Prima di debuggare qualsiasi altra cosa, testa quell'URL con una sessione browser nuova.
  • Content type del corpo della richiesta. Invia JSON con Content-Type: application/json. Se dimentichi -H quando invii con curl, il server potrebbe non analizzare affatto il corpo della richiesta.
  • Polling troppo aggressivo. Un while True senza sleep ti fa finire in rate limit e non rende il render più veloce. Backoff più scadenza sono l'intero trucco.
  • Timeout assenti ovunque. Imposta timeout sulle chiamate di invio, polling e download. Un timeout mancante trasforma un provider lento in una coda bloccata nel tuo stesso sistema.
  • Il prompt descrive il movimento, non l'immagine. Descrivi ciò che cambia tra un frame e l'altro — movimento di camera, azioni del soggetto, elementi ambientali — e mantienilo breve. Ripetere l'immagine statica non aggiunge nulla.
  • Una configurazione, più modelli. Metti ID del modello, prompt e input immagine nello stesso builder del payload. Passare da questo livello veloce a uno più pesante richiede solo la modifica di una stringa.

Il modo più rapido per verificare la configurazione end-to-end è: esegui l'invio con curl, incolla l'ID dell'attività nel ciclo di polling e conferma che un MP4 arrivi su disco — una volta che questo funziona, il resto è solo impacchettamento.

Inizia a creare con artefatti IA persistenti

Ottieni la tua chiave API e chiama modelli di immagini, video e audio tramite un'unica API basata sui task.

Leggi la documentazione API