HiAPI
Blog

Tutoriale

Come chiamare veo-3.1-lite/text-to-video tramite l'API HiAPI: curl e Python

Esempi di richiesta pronti da copiare, il flusso da prompt a video e il pattern di polling asincrono

Nov 3, 2026
深炭底色上,一道琥珀色光带横向掠过,构图克制,是这篇教程文章的封面图。

Cosa offre veo-3.1-lite/text-to-video

veo-3.1-lite/text-to-video è un endpoint di generazione video da testo su HiAPI: invii un prompt testuale e il modello restituisce una clip video generata. Il "lite" nel nome è il segnale più importante quando scegli il modello. Il livello leggero è pensato per il throughput e l'iterazione rapida, non per la massima fedeltà, quindi è un'ottima scelta predefinita quando ti servono molte clip brevi, quando vuoi prototipare una funzionalità prima di impegnarti con un modello più pesante, o quando la clip è solo un tassello di una pipeline di automazione più ampia in cui latenza per clip e volume batch contano più della qualità cinematografica.

Quando la clip è il prodotto stesso — una ripresa hero, materiale di marketing, o qualsiasi cosa che verrà esaminata fotogramma per fotogramma da una persona — conviene passare a un modello text-to-video più pesante. Quando invece la clip è solo un componente — un'anteprima di bozza, un loop di sfondo, uno storyboard grezzo, o un primo render che verrà poi rifinito — scegli veo-3.1-lite/text-to-video.

Due caratteristiche di questo endpoint determinano tutto ciò che segue:

  • È asincrono. Una richiesta di generazione non ti restituisce direttamente un file riproducibile nella stessa risposta. Invii un job, ricevi un riferimento a quel job, poi esegui il polling finché il job non è completato e l'URL dell'output è disponibile.
  • È guidato dal prompt. Non ci sono immagini di input né video sorgente in questo flusso — il testo che invii è il brief creativo completo, quindi la qualità del prompt è la leva principale che controlli.

Il resto di questa guida è un percorso operativo: costruire la richiesta, inviarla con curl, portarla in Python, fare polling correttamente e gestire i casi di errore che incontrerai davvero.

Prerequisiti e autenticazione

Prima della prima chiamata, assicurati di avere:

  • Una API key dal tuo account HiAPI. Trattala come una password del database: come un segreto.
  • Un modo per inviare richieste HTTP: curl per verifiche rapide, Python 3 con la libreria requests per qualsiasi attività scriptata.
  • Una destinazione per la clip finale, perché l'output arriva come URL e dovrai scaricarlo.

La chiave viene passata in ogni richiesta tramite header HTTP. Copia esattamente il nome dell'header e il prefisso Bearer come mostrato; un header mancante o malformato è la causa più comune di fallimento alla prima chiamata.

Tieni la chiave fuori dal controllo di versione

Non incollare mai una chiave reale in un file che committerai. Usa invece una variabile d'ambiente:

export HIAPI_API_KEY="your-key-here"

Poi leggila a runtime. Se lavori in un team o in una pipeline CI, conserva la chiave in un secret manager e iniettala come variabile d'ambiente al deploy. Se la chiave è mai stata esposta — per un commit accidentale, incollata in un ticket, stampata nei log — ruotala subito, invece di sperare che nessuno se ne accorga.

Prima di iniziare a fare debug su qualsiasi altra cosa, verifica rapidamente che la variabile d'ambiente sia impostata:

test -n "$HIAPI_API_KEY" && echo "key is set" || echo "key is missing"

Una richiesta curl funzionante

Ecco la forma della richiesta di invio text-to-video. Sostituisci il placeholder dell'URL base dell'endpoint con l'indirizzo documentato nella tua documentazione di riferimento HiAPI, e mantieni l'identificatore del modello invariato: veo-3.1-lite/text-to-video.

curl -X POST "$HIAPI_BASE_URL/v1/videos/generations" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "veo-3.1-lite/text-to-video",
    "prompt": "A slow dolly-in on a rain-soaked neon sign at night, shallow depth of field, gentle camera drift, cinematic color"
  }'

Un invio riuscito non restituisce byte video. Restituisce un oggetto job: contenente l'identificatore del job, un campo status con valore iniziale come queued o processing, e metadati sul momento di creazione. Quell'identificatore è l'handle per tutto ciò che segue — devi fare polling su di esso finché lo stato non diventa terminale, e solo allora la risposta conterrà l'URL della clip generata.

Se stai testando manualmente, cattura la risposta completa invece di limitarti a guardarla:

curl -s -X POST "$HIAPI_BASE_URL/v1/videos/generations" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"veo-3.1-lite/text-to-video","prompt":"A paper boat drifting down a sunlit gutter, macro lens"}' \
  | tee submission.json

Due abitudini valgono la pena di essere adottate subito. Primo, invia sempre Content-Type: application/json; un corpo che sembra JSON ma non è dichiarato come JSON verrà rifiutato. Secondo, se la chiamata fallisce, leggi il corpo della risposta di errore prima di modificare qualsiasi cosa — HiAPI restituisce messaggi strutturati che di solito indicano il problema (chiave errata, modello sconosciuto, campo mancante) con più precisione del solo codice di stato.

Se qui arriva uno stato non 2xx, il job non è mai stato accettato. Non fare polling su un invio fallito; correggi prima la richiesta.

Implementare la stessa richiesta in Python

La chiamata curl si trasporta direttamente. Lo script seguente invia il prompt, verifica che l'invio sia andato a buon fine e salva il riferimento del job per il passo di polling.

import os
import requests

BASE_URL = os.environ["HIAPI_BASE_URL"]
API_KEY = os.environ["HIAPI_API_KEY"]

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

payload = {
    "model": "veo-3.1-lite/text-to-video",
    "prompt": (
        "A single lantern floating on still black water, "
        "soft ripples, warm light against cool darkness, slow push-in"
    ),
}

response = requests.post(
    f"{BASE_URL}/v1/videos/generations",
    headers=headers,
    json=payload,
    timeout=30,
)

if response.status_code >= 400:
    raise RuntimeError(f"Submission failed: {response.status_code} {response.text}")

job = response.json()
job_id = job["id"]
print(f"submitted job {job_id} with status {job.get('status')}")

Ci sono alcune scelte intenzionali in questo frammento:

  • Usa json=payload invece di concatenare stringhe a mano. Lascia che la libreria gestisca la serializzazione e il content type; questo evita un'intera classe di bug legati alle virgolette.
  • Imposta esplicitamente timeout. Senza, una connessione bloccata farebbe restare lo script appeso all'infinito. La chiamata di invio è veloce — anche un timeout generoso qui resta breve.
  • Fallisce duramente su non-2xx. Sollevare un'eccezione subito mantiene l'errore vicino alla sua causa, invece di lasciare che un job ID corrotto si propaghi nel ciclo di polling.
  • La chiave viene da os.environ. Il file stesso non contiene alcun segreto.

Racchiudilo in una funzione che restituisce job_id e avrai la parte di invio di qualsiasi pipeline.

Polling del job asincrono e download della clip

La generazione video richiede tempo. La chiamata di invio ritorna in uno o due secondi; il rendering effettivo no. Se scrivi codice che presume che la prima risposta contenga l'URL del video, fallirà — non perché l'API sia rotta, ma perché stai leggendo un job non ancora completato.

Il pattern corretto è il polling con limite: dormi tra una verifica e l'altra, aumenta l'attesa man mano che il tempo passa e rinuncia dopo un numero massimo di tentativi invece di ciclare all'infinito.

import time
import requests

def wait_for_video(job_id, headers, base_url, max_attempts=60, initial_delay=5):
    delay = initial_delay
    for attempt in range(1, max_attempts + 1):
        r = requests.get(
            f"{base_url}/v1/videos/generations/{job_id}",
            headers=headers,
            timeout=30,
        )
        if r.status_code >= 400:
            raise RuntimeError(f"Poll failed: {r.status_code} {r.text}")

        data = r.json()
        status = data.get("status")
        print(f"attempt {attempt}: {status}")

        if status == "completed":
            return data
        if status in {"failed", "cancelled"}:
            raise RuntimeError(f"Job {job_id} ended as {status}: {data}")

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

    raise TimeoutError(f"Job {job_id} did not finish within polling budget")

Quando il job raggiunge completed, la risposta porta la posizione dell'output — l'URL della clip completata. Se la clip è grande, scaricala invece di leggerla in streaming in memoria:

result = wait_for_video(job_id, headers, BASE_URL)
video_url = result["output"]["url"]

with requests.get(video_url, stream=True, timeout=120) as r:
    r.raise_for_status()
    with open(f"{job_id}.mp4", "wb") as f:
        for chunk in r.iter_content(chunk_size=1 << 16):
            f.write(chunk)

print("saved", f"{job_id}.mp4")

Due dettagli contano in produzione. Primo, l'URL dell'output può avere una scadenza, quindi scarica tempestivamente invece di salvare il link e recuperarlo giorni dopo — conserva il file o copialo nel tuo storage. Secondo, registra l'ID del job accanto a ogni artefatto. Quando qualcosa va storto a valle, l'ID del job ti permette di confrontare i tuoi registri con ciò che l'API ha effettivamente restituito.

Errori comuni e come risolverli

La maggior parte dei fallimenti alla prima esecuzione ricade in poche categorie. Ecco come si manifesta ciascuna e cosa cambiare.

Fallimento di autenticazione

Un 401 o 403 all'invio indica che la chiave non è stata accettata. Controlla nell'ordine: se la variabile d'ambiente ha davvero un valore in quella shell o in quel processo; se il nome dell'header è Authorization; se il prefisso Bearer è presente e seguito da uno spazio; se la chiave è stata ruotata o revocata. Una chiave che funziona nel tuo terminale ma fallisce in CI quasi sempre significa che il segreto non è stato iniettato in quell'ambiente.

Identificatore del modello non valido

Un 400 o 404 che indica il nome del modello significa che la stringa non corrisponde. L'identificatore è veo-3.1-lite/text-to-video — la barra e il numero di versione fanno parte di esso. Copia e incolla, non ridigitare; uno spazio in più o una versione modificata basta a far fallire tutto.

Prompt malformato o vuoto

Un prompt vuoto, un campo prompt mancante, o un corpo inviato senza Content-Type: application/json verranno rifiutati prima che la generazione inizi. Se costruisci il prompt dinamicamente, registra la stringa esatta che hai inviato quando la chiamata fallisce — i bug nei template che producono prompt vuoti o troncati sono comuni e invisibili finché non li stampi.

Timeout del polling

Se wait_for_video esaurisce il budget, non reinviare subito. Il job potrebbe semplicemente essere ancora in esecuzione. Ricontrolla lo stato una volta, verifica che il tuo intervallo di polling non sia così lungo da spendere tutto il budget in attese, e che il budget sia realistico per la durata della clip richiesta. Reinviare a ogni timeout è esattamente il modo in cui paghi accidentalmente più volte la stessa clip.

Rate limit

Un 429 significa che devi fare backoff. Il ritardo esponenziale nel ciclo di polling aiuta già; applica la stessa moderazione anche all'invio, e metti in coda il lavoro invece di inviarlo a raffica. In caso di dubbio, segui prima gli eventuali suggerimenti di retry presenti negli header della risposta, poi il tuo piano di backoff.

Consigli su costi e iterazione prima della produzione

La generazione video viene fatturata sull'output — cioè sulla lunghezza del contenuto prodotto — quindi l'errore costoso è usare clip lunghe per testare piccole idee. Il flusso di lavoro più economico è quello che non genera mai una clip di cui non hai bisogno.

Testa la struttura del prompt, non l'output finale. Scrivi e affina i prompt sulla durata più breve supportata dall'endpoint. Una volta che composizione, movimento e linguaggio di ripresa si leggono correttamente in una clip breve, aumenta la durata per il render finale. La maggior parte delle iterazioni sui prompt riguarda la formulazione, non la lunghezza, e un render breve risponde ugualmente alle domande sulla formulazione.

Tieni un registro dei prompt. Annota per ogni test il prompt, la durata e il tuo giudizio. Dieci iterazioni documentate ti insegnano di più su ciò a cui il modello reagisce rispetto a cinquanta non documentate, e non ritesti prompt già scartati.

Elabora in batch con criterio. Se ti servono molte clip, mettile in coda ed eseguile in parallelo entro i rate limit — ma fai prima un'esecuzione di prova dell'intero batch con pochi prompt. Scoprire un bug sistematico nel prompt dopo aver inviato duecento job è il costo più evitabile.

Fai polling con moderazione. Polling frequente non rende il render più veloce e può spingerti contro i rate limit. Un intervallo iniziale moderato più un backoff gentile sono il default corretto.

Fallisci presto e in modo evidente. Ogni protezione nel codice sopra — eccezioni su non-2xx all'invio, rilevamento degli stati terminali di fallimento nel polling, numero di tentativi limitato — serve a evitarti di pagare due volte per lo stesso lavoro. Mantienile in produzione.

Inizia con un prompt breve e una richiesta curl, verifica end-to-end il ciclo di vita del job, poi porta lo stesso flusso nella tua pipeline Python. Il riferimento completo dell'endpoint è nella pagina di documentazione del modello veo-3.1-lite/text-to-video su HiAPI.

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