HiAPI
Blog

Tutoriale

Come usare l'API image-to-video di hailuo 2.3 Fast:curl, Python e richieste eseguibili

Jul 10, 2026
Come usare l'API image-to-video di hailuo 2.3 Fast:curl, Python e richieste eseguibili

Hai un'immagine statica e vuoi trasformarla in un breve video. Questa guida ti mostra come accedere a hailuo-2.3-fast/image-to-video tramite l'API a task di hiapi: lo schema di input esatto (verificato contro il validatore di produzione), una richiesta curl pronta da copiare, uno script Python completo che esegue il polling fino al completamento del task e i dettagli per la produzione — callback, retry idempotenti e le risposte di errore che incontrerai davvero.

Cosa ti serve

  • Una chiave API hiapi — ottienila dalla tua console. La chiave ha la forma sk-... e va inserita nell'header Authorization: Bearer.
  • Un'immagine sorgente ospitata su un URL HTTPS accessibile pubblicamente. L'API riceve un URL, non un file caricato.
  • Qualsiasi client HTTP. Gli esempi seguenti usano curl e requests di Python.

Il prezzo al secondo di questo modello è indicato nella pagina dei prezzi.

Schema di input (verificato)

hailuo-2.3-fast/image-to-video usa l'endpoint unificato per task asincroni: POST /v1/tasks crea il task, GET /v1/tasks/<taskId> ne interroga lo stato. Lo schema di input è rigoroso — i campi sconosciuti vengono rifiutati con 400, non ignorati silenziosamente.

Campo Tipo Obbligatorio Descrizione
prompt string ✅ Descrive il movimento desiderato
image_url string ✅ Forma singolare — una stringa URL, non un array image_urls
duration string Opzionale "6" o "10" — è una stringa, non un numero
prompt_optimizer boolean Opzionale Fa riscrivere il prompt dal backend per il movimento

Il validatore intercetta tre trappole comuni:

  1. duration deve essere una stringa. Inviare "duration": 6 restituisce 400 con il messaggio duration: got number, want string. Invia "duration": "6".
  2. Il campo è image_url al singolare. Molti altri modelli video della piattaforma (Grok Imagine, Kling) ricevono un array image_urls. Questo modello riceve una singola stringa.
  3. Non esistono resolution, aspect_ratio, seed, watermark né end_image_url. Tutti restituiscono additional properties ... not allowed. Il formato di output segue l'immagine sorgente; il controllo del movimento avviene solo tramite il prompt.

Richiesta minima eseguibile: curl

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "hailuo-2.3-fast/image-to-video",
    "input": {
      "prompt": "La camera avanza lentamente, vapore che sale dalla tazza di caffè, morbida luce mattutina",
      "image_url": "https://your-cdn.example.com/coffee.jpg",
      "duration": "6"
    }
  }'

Risposta di successo:

{
  "code": 200,
  "data": { "taskId": "tk-hiapi-01KX4W3SVW8S8EHD5VX45JH95V" },
  "message": "success"
}

Poi esegui il polling:

curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-01KX4W3SVW8S8EHD5VX45JH95V \
  -H "Authorization: Bearer sk-YOUR_KEY"

Mentre il video viene renderizzato, data.status è "handling":

{
  "code": 200,
  "data": {
    "taskId": "tk-hiapi-01KX4W3SVW8S8EHD5VX45JH95V",
    "model": "hailuo-2.3-fast/image-to-video",
    "status": "handling",
    "storage": "temp",
    "completed": 0,
    "created": 1783648872
  },
  "message": "success"
}

Gli stati terminali sono "success" (l'URL del video è in data.output[0].url) e "fail" (i dettagli sono in data.error).

Script Python completo

import os
import time

import requests

API_BASE = "https://api.hiapi.ai/v1/tasks"
TOKEN = os.environ["HIAPI_API_KEY"]  # sk-...
HEADERS = {"Authorization": f"Bearer {TOKEN}"}


def create_task(prompt: str, image_url: str, duration: str = "6") -> str:
    resp = requests.post(
        API_BASE,
        headers={**HEADERS, "Content-Type": "application/json"},
        json={
            "model": "hailuo-2.3-fast/image-to-video",
            "input": {
                "prompt": prompt,
                "image_url": image_url,
                "duration": duration,  # stringa: "6" o "10"
            },
        },
        timeout=60,
    )
    body = resp.json()
    if resp.status_code != 200 or not body.get("data", {}).get("taskId"):
        raise RuntimeError(f"create failed: {resp.status_code} {body}")
    return body["data"]["taskId"]


def wait_task(task_id: str, timeout_s: int = 600, poll_s: int = 5) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        task = requests.get(f"{API_BASE}/{task_id}", headers=HEADERS, timeout=30).json()["data"]
        status = task.get("status")
        if status == "success":
            return task
        if status == "fail":
            err = task.get("error") or {}
            raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
        time.sleep(poll_s)
    raise TimeoutError(f"task {task_id} still running after {timeout_s}s")


if __name__ == "__main__":
    task_id = create_task(
        prompt="Il gatto sbatte le palpebre, gira la testa verso la finestra, una brezza gli muove il pelo",
        image_url="https://your-cdn.example.com/cat.jpg",
        duration="6",
    )
    print("task:", task_id)

    task = wait_task(task_id)
    video_url = task["output"][0]["url"]
    print("video:", video_url)

    # L'URL si trova su storage temporaneo con scadenza — scarica subito, non collegarlo direttamente.
    mp4 = requests.get(video_url, timeout=120).content
    with open("output.mp4", "wb") as f:
        f.write(mp4)
    print(f"saved output.mp4 ({len(mp4)} bytes)")

Questo script fa correttamente due cose che le soluzioni improvvisate spesso trascurano:

  • Scarica subito il video. storage: "temp" significa che l'URL di output ha una scadenza (expireAt). Salva i byte nel tuo storage; non inserire mai l'URL restituito nel tuo database.
  • Considera fail uno stato terminale. Ripetere il polling di un task fallito non lo riporta in vita — crea un nuovo task.

Considerazioni per la produzione

Usa le callback invece del polling

Per casi d'uso al di fuori degli script, registra un webhook alla creazione del task e salta del tutto il ciclo di polling:

{
  "model": "hailuo-2.3-fast/image-to-video",
  "input": { "prompt": "...", "image_url": "https://...", "duration": "6" },
  "callback": { "url": "https://your-app.example.com/hooks/hiapi", "when": "final" }
}

Con "when": "final", il tuo endpoint viene chiamato una volta quando il task raggiunge success o fail. Regola pratica: il polling va bene per CLI, job batch e qualsiasi scenario temporaneo; quando hai già un server in ascolto, le callback sono preferibili — i task video richiedono alcuni minuti, e un ciclo di polling in quei minuti occupa uno slot di connessione e spreca un worker.

Idempotenza

La chiamata di creazione non è idempotente — ogni POST /v1/tasks che supera la validazione crea un nuovo task (a pagamento). Se il tuo task runner esegue retry al timeout, memorizza il taskId ottenuto con chiave il tuo id di richiesta prima di elaborare il risultato. In caso di retry, se quella chiave ha già un taskId, esegui il polling su di esso invece di creare un task duplicato.

Gli errori che incontrerai davvero

Sintomo Significato Correzione
401 — {"error":{"code":"permission_denied","type":"hiapi_error"}} Chiave non valida, o la chiave non può usare questo modello Controlla la chiave nella console; la risposta include un request_id da fornire al supporto
400 INVALID_REQUEST — duration: got number, want string Hai usato un numero JSON invece di una stringa "duration": "6"
400 INVALID_REQUEST — additional properties '...' not allowed Hai inviato un campo al di fuori dello schema Rimuovilo — il messaggio indica ogni campo non valido
400 INVALID_REQUEST — missing required field "prompt" / "image_url" Input vuoto o nome del campo errato Entrambi i campi sono obbligatori
Il task arriva a fail dopo la creazione Errore in fase di esecuzione — causa comune un URL immagine che il backend non riesce a recuperare Il validatore non recupera la tua immagine alla creazione; assicurati che l'URL sia accessibile pubblicamente

L'ultima riga merita enfasi: la validazione alla creazione riguarda solo lo schema. Un image_url sbagliato o privato ti restituisce un taskId apparentemente valido e un task che muore dopo qualche minuto — quindi configura avvisi su fail, non solo sugli errori HTTP.

Letture correlate

Domande frequenti

Posso impostare risoluzione o rapporto d'aspetto? No. Lo schema di hailuo-2.3-fast/image-to-video rifiuta resolution e aspect_ratio come campi sconosciuti. Il formato di output segue l'immagine sorgente; se ti serve un rapporto d'aspetto specifico, ritaglia prima l'immagine di input.

Quali durate sono supportate? Esattamente due: "6" e "10" secondi, passati come stringhe. Qualsiasi altro valore restituisce 400 con il messaggio value must be one of '6', '10'.

Posso passare più immagini di riferimento? No — il campo è un singolo image_url stringa. I modelli che accettano array di immagini (ad esempio image_urls di Grok Imagine) usano uno schema diverso; non copiare il body di richiesta tra modelli senza controllare.

Come controllo il movimento? Solo tramite prompt — non esistono parametri per intensità di movimento o camera. Descrivi il movimento in modo esplicito ("la camera avanza lentamente", "i capelli ondeggiano al vento"). Facoltativamente, imposta "prompt_optimizer": true per far espandere il prompt dal backend.

Esiste una variante più lenta e di qualità superiore? Sì — hailuo-2.3/image-to-video (senza -fast) è anch'esso disponibile sulla piattaforma, e hailuo-2.3/text-to-video copre gli scenari senza immagine sorgente. Controlla lo schema di ciascuna variante prima di passare da una all'altra; i campi di input non sono garantiti identici.

Quanto costa? La fatturazione si basa sui secondi di video generato e varia in base al modello; consulta le tariffe attuali nella pagina dei prezzi.

Serve una chiave API per provare? Sì — tutte le chiamate all'API a task richiedono una chiave Bearer sk-.... Puoi crearne una nella console e testarla prima nel playground della pagina del modello.

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