HiAPI
Blog

Tutoriale

Come usare l'API di text-to-video Ray 3.2: curl, Python e una richiesta eseguibile subito

Oct 20, 2026
Come usare l'API di text-to-video Ray 3.2: curl, Python e una richiesta eseguibile subito

Ray 3.2 è un modello text-to-video del catalogo modelli di hiapi e, come tutti gli altri modelli video, si richiama tramite l'API asincrona a task unificata: l'id del modello è ray-3.2/text-to-video e si invia a POST /v1/tasks. A differenza di alcuni modelli video in cui l'unico controllo è il prompt, Ray 3.2 offre tre controlli reali sull'output — duration, resolution e aspect_ratio — ciascuno validato rigorosamente lato server.

Questa guida ti permette di inviare subito una richiesta che va a buon fine: lo schema di input preciso (verificato contro l'API di produzione il 2026-10-19), una chiamata curl da copiare e incollare, uno script Python completo per invio/polling/download e i dettagli per la produzione — callback, retry idempotenti e le risposte di errore che incontrerai davvero.

Cosa costruirai e cosa ti serve

Obiettivo: inviare un prompt testuale, fare polling del task e scaricare una clip .mp4 senza audio (da 5 o 10 secondi).

Prerequisiti:

  • Una chiave API hiapi — ottienila dalla tua console. La chiave ha la forma sk-... e va inserita nell'header Authorization: Bearer. Nuovo alle API a task? Inizia dalla guida rapida.
  • Un qualsiasi client HTTP. Mostreremo curl e Python (requests).

L'output di Ray 3.2 è fatturato per clip, e il prezzo cresce notevolmente con le tue scelte — consulta le tariffe attuali nella pagina dei prezzi. Quando iteri sul prompt, parti da 540p / 5 secondi; una clip 1080p da 10 secondi può costare molte volte di più di un rendering di immagini contenuto nel budget, quindi non lasciare il tuo ciclo di sviluppo sempre impostato al massimo.

Lo schema di input esatto (un campo obbligatorio, tre enum rigorosi)

Tutto va dentro un oggetto input. Verificato campo per campo contro l'API di produzione:

Campo Tipo Obbligatorio Valori ammessi Valore predefinito
prompt string Sì Descrizione della scena, massimo 6000 caratteri —
duration integer No 5 o 10 5
resolution string No 540p, 720p, 1080p 540p
aspect_ratio string No 16:9, 9:16, 1:1, 4:3, 3:4, 21:9 16:9

Prima di scrivere codice, tre cose da tenere a mente:

  1. duration è un intero, non una stringa. Invia un numero nudo — "duration": 5. È l'opposto di alcuni altri modelli video dell'API a task (ad esempio hailuo-2.3 richiede "duration": "6" come stringa). Se porti un body da un modello a un altro, ricontrolla questo campo; è l'errore cross-modello più comune.
  2. I valori enum sono validati, non troncati. I valori fuori intervallo vengono rifiutati con 400, non arrotondati silenziosamente. Non esiste 2k, né 480p, né 2:1.
  3. Non esistono prompt_optimizer, seed o negative_prompt. La composizione si controlla interamente tramite aspect_ratio più il prompt. Se invii campi che lo schema non riconosce, aspettati un 400 — lo schema di input di /v1/tasks varia da modello a modello, quindi non dare per scontato che chiavi valide altrove siano riutilizzabili qui.

Cosa restituisce davvero la validazione

Ecco risposte reali dell'endpoint di produzione (invio deliberato di valori non validi, nessun task creato, nessun addebito):

// POST with "resolution": "2k"
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
 "message":"invalid input: resolution: value must be one of '540p', '720p', '1080p'"}

// POST with "duration": 7
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
 "message":"invalid input: duration: value must be one of 5, 10"}

// POST with "aspect_ratio": "2:1"
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
 "message":"invalid input: aspect_ratio: value must be one of '16:9', '9:16', '1:1', '4:3', '3:4', '21:9'"}

Il message indica il campo esatto e l'insieme esatto dei valori ammessi — quindi se dimentichi un valore enum, l'API te lo dice. Nota che i valori 5, 10 di duration sono senza virgolette (numeri), mentre '540p' di resolution è una stringa tra virgolette: questa differenza nelle virgolette ti ricorda quale campo è un intero.

Esempio minimo eseguibile: curl

Crea il task:

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ray-3.2/text-to-video",
    "input": {
      "prompt": "Di notte sotto la pioggia, una ballerina di strada gira sotto le luci al neon, le pozzanghere riflettono luci rosa e blu, la sua giacca svolazza mentre ruota, la camera la circonda dal basso, atmosfera cinematografica",
      "duration": 5,
      "resolution": "540p",
      "aspect_ratio": "9:16"
    }
  }'

La risposta di creazione include un id del task (troncato):

{ "data": { "taskId": "task_01k..." } }

Esegui il polling finché non raggiunge uno stato terminale:

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

Mentre il task è in esecuzione, data.status riporta uno stato in corso. Gli stati terminali sono success e fail. In caso di successo (troncato):

{
  "data": {
    "taskId": "task_01k...",
    "status": "success",
    "output": [ { "url": "https://.../clip.mp4" } ]
  }
}

Scarica subito data.output[0].url. Gli URL di output sono firmati e scadono — salva i byte nel tuo storage invece di fare hotlink diretto all'URL del task. Ray 3.2 restituisce un MP4 senza audio (nessuna traccia audio), quindi non aspettare un flusso audio che non arriverà mai.

Script Python completo (invio → polling → download)

import time
import requests

API_BASE = "https://api.hiapi.ai/v1"
API_KEY = "sk-YOUR_KEY"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}


def create_task(prompt: str, duration: int = 5,
                resolution: str = "540p", aspect_ratio: str = "9:16") -> str:
    resp = requests.post(
        f"{API_BASE}/tasks",
        headers=HEADERS,
        json={
            "model": "ray-3.2/text-to-video",
            "input": {
                "prompt": prompt,
                "duration": duration,          # 5 or 10 — an INTEGER, not a string
                "resolution": resolution,      # "540p" | "720p" | "1080p"
                "aspect_ratio": aspect_ratio,  # "16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "21:9"
            },
        },
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()["data"]["taskId"]


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


task_id = create_task(
    "Una barchetta di carta che va alla deriva lungo un canale di scolo pieno di pioggia, "
    "le gocce creano increspature sulla superficie, foglie autunnali vorticano passando, "
    "ripresa dal basso vicino all'acqua, luce morbida di una giornata nuvolosa"
)
print("task created:", task_id)

task = wait_task(task_id)
video_url = task["output"][0]["url"]

# Output URLs expire — download right away and store the bytes yourself.
video = requests.get(video_url, timeout=120).content
with open("ray32-clip.mp4", "wb") as f:
    f.write(video)
print(f"saved ray32-clip.mp4 ({len(video) / 1e6:.1f} MB)")

Installa le dipendenze con pip install requests, inserisci la tua chiave e al termine del task avrai un .mp4 sul disco. I task video richiedono minuti, non secondi — dai al tuo ciclo di polling una scadenza ampia (sopra sono 15 minuti) invece di dare per scontato che torni subito.

Considerazioni per la produzione: callback, idempotenza, errori

Preferisci le callback al polling

Per qualsiasi uso oltre gli script, registra un webhook al momento della creazione del task invece di fare polling:

{
  "model": "ray-3.2/text-to-video",
  "input": { "prompt": "...", "duration": 10, "resolution": "720p" },
  "callback": { "url": "https://your-app.example.com/hooks/hiapi", "when": "final" }
}

Con "when": "final", hiapi chiama il tuo endpoint una volta quando il task raggiunge lo stato terminale — nessun traffico di polling e nessuna notifica di completamento persa tra un intervallo e l'altro. Il tuo handler esegue quindi GET /v1/tasks/<id> (o legge il body della consegna) per ottenere output[0].url. Per CLI e notebook che non possono esporre un endpoint HTTPS, il polling resta valido.

Mantieni idempotenti i retry

Persisti il taskId nel momento in cui la chiamata di creazione ritorna. Se il tuo processo si rompe a metà della generazione, riprendi facendo polling sull'id salvato — non ricreare il task, altrimenti pagherai per un secondo rendering. Ricrea solo se la richiesta di creazione originale stessa è fallita prima di restituire l'id del task. Poiché Ray 3.2 è fatturato per clip e le clip ad alta risoluzione non sono economiche, la creazione duplicata è un errore di costo concreto, non solo una perdita di tempo.

Gli errori che vedrai davvero

Ogni riga qui sotto è una forma di risposta di produzione verificata:

Sintomo Risposta Come risolvere
Chiave mancante/non valida 401, annidata {"error":{"code":"permission_denied","type":"hiapi_error","request_id":"..."}} Controlla l'header Authorization: Bearer sk-... e che la chiave abbia accesso. Fornisci request_id quando contatti il supporto.
Valore enum errato 400 INVALID_REQUEST, piatta {"code":400,"error_code":"INVALID_REQUEST","message":"invalid input: <field>: value must be one of ..."} Leggi il campo indicato nel message; elenca l'insieme esatto dei valori ammessi.
duration inviato come stringa 400 INVALID_REQUEST Invia un intero nudo 5 o 10, non "5".
Id modello sconosciuto 400, error_code: "MODEL_UNAVAILABLE", "model not available via /v1/tasks" Usa esattamente ray-3.2/text-to-video — controlla l'id canonico nella pagina del modello.
Id task errato nel polling 404, piatta {"code":404,"data":null,"message":"task not found"} (nota: sul 404 non c'è error_code) Id errato, oppure stai interrogando con la chiave di un altro account.
status: "fail" nel polling Task in stato terminale, dettagli in data.error Leggi data.error.code / data.error.message. I task falliti vengono rimborsati; flag di policy sui contenuti e problemi di capacità a monte emergono qui — per i flag sui contenuti, riprova con un prompt riformulato.

Risorse correlate

Domande frequenti

Qual è l'id del modello e quale endpoint devo chiamare? ray-3.2/text-to-video, inviato come model di primo livello a POST https://api.hiapi.ai/v1/tasks, con i parametri sotto input. È asincrono — ricevi un taskId, poi fai polling su GET /v1/tasks/<id> (oppure registra una callback).

duration è in secondi e perché "5" fallisce? Le clip durano 5 o 10 secondi e il campo è un intero: invia 5 o 10. Una stringa tra virgolette, o qualsiasi altro numero come 7, fallisce con 400 INVALID_REQUEST: duration: value must be one of 5, 10.

Posso impostare risoluzione e aspect ratio? Sì — è proprio ciò che distingue Ray 3.2 dai modelli video solo-prompt. resolution è 540p, 720p o 1080p; aspect_ratio è uno tra 16:9, 9:16, 1:1, 4:3, 3:4, 21:9. Entrambi sono opzionali (predefiniti 540p e 16:9) e rifiutano i valori fuori intervallo con un 400.

Quanto costa una clip? Ray 3.2 è fatturato per clip e la risoluzione è la leva più importante — a parità di durata, una clip 1080p costa diverse volte una 540p, e una clip da 10 secondi costa circa tre volte una da 5 secondi. Le tariffe cambiano, quindi consulta la pagina dei prezzi invece di codificare un numero fisso, e fai il prototipo a 540p/5 secondi.

Le clip hanno l'audio? No — il text-to-video di Ray 3.2 restituisce un MP4 senza audio. Se ti serve il suono, aggiungilo in post-produzione o usa un modello audio separato.

Ray 3.2 supporta l'image-to-video? Sì, come id modello separato sulla stessa API a task: ray-3.2/image-to-video. Il suo schema di input è diverso (accetta un'immagine di riferimento), quindi sonda prima di riutilizzare questo body — vedi la pagina del modello image-to-video.

La mia generazione è lenta o sembra bloccata. Cosa è normale? I task video durano alcuni minuti. Nel tuo ciclo di polling lascia passare gli stati non terminali di data.status e attendi — prevedi una scadenza di 10–15 minuti, oppure usa una callback con "when": "final" così hiapi ti notifica allo stato terminale invece di indovinare un intervallo.

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