Tutoriale
API happyhorse 1.1 da testo a video: tutorial curl e Python funzionante
Questa pagina ti guiderà attraverso la richiesta minima per generare un video con happyhorse-1.1/text-to-video, spiegando quali valori accetta effettivamente ogni campo di input e i due modi per attendere il risultato (polling e callback). Tutto quanto segue è stato testato sull'API live; le strutture delle richieste e i messaggi di errore sono riportati testualmente.
Cosa costruirai
Una funzione: riceve un prompt testuale e restituisce un URL MP4 scaricabile. Il modello è happyhorse-1.1/text-to-video, erogato tramite l'endpoint unificato /v1/tasks. Invii un task, ricevi immediatamente un taskId, poi esegui il polling fino al completamento oppure lasci che hiapi invii il risultato finale via POST al tuo webhook.
Prerequisiti:
- Una chiave API hiapi. I nuovi account ricevono 1 dollaro di credito gratuito — ottienila nella pagina API Keys.
- Un modo per effettuare richieste HTTPS. Non è necessario installare alcun SDK.
Non inserire la chiave nel codice client e non fare commit su git. Leggila da una variabile d'ambiente:
export HIAPI_API_KEY="sk-..."
Richiesta minima funzionante
L'unico campo di input obbligatorio è prompt. Tutti gli altri campi hanno valori predefiniti.
curl
curl -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "happyhorse-1.1/text-to-video",
"input": {
"prompt": "a calm ocean wave at sunset, slow motion, golden hour"
}
}'
Risposta:
{
"code": 200,
"data": { "taskId": "tk-hiapi-01KW23VDG2EZ20J7RQH8S67MNC" },
"message": "success"
}
Questo taskId è la tua credenziale per operare sul task. Conservalo con cura.
Python (solo libreria standard)
import os
import urllib.request
import json
API_KEY = os.environ["HIAPI_API_KEY"]
def submit_task(prompt: str, **input_overrides) -> str:
body = {
"model": "happyhorse-1.1/text-to-video",
"input": {"prompt": prompt, **input_overrides},
}
req = urllib.request.Request(
"https://api.hiapi.ai/v1/tasks",
method="POST",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
data=json.dumps(body).encode("utf-8"),
)
with urllib.request.urlopen(req) as resp:
payload = json.loads(resp.read())
if payload.get("code") != 200:
raise RuntimeError(payload)
return payload["data"]["taskId"]
task_id = submit_task("a calm ocean wave at sunset, slow motion, golden hour")
print(task_id) # e.g. tk-hiapi-01KW23VDG2EZ20J7RQH8S67MNC
Tabella completa dei parametri
La struttura dei parametri è piccola e rigorosa — i campi sconosciuti vengono rifiutati con 400, non scartati silenziosamente. Di seguito sono elencati i campi effettivamente accettati all'interno di input, presi direttamente dai messaggi di errore di validazione:
| Campo | Tipo | Obbligatorio | Valori ammessi | Comportamento predefinito |
|---|---|---|---|---|
prompt |
string | Sì | Qualsiasi testo non vuoto che descriva la scena | — |
aspect_ratio |
string | No | 16:9, 9:16, 3:4, 4:3, 4:5, 5:4, 1:1, 9:21, 21:9 |
Valore predefinito del modello |
resolution |
string | No | 720p, 1080p |
Valore predefinito del modello |
duration |
integer | No | Da 3 a 15 (secondi) | Valore predefinito del modello |
Campi che sembrano ovvi ma al momento non sono accettati: size, audio, seed, negative_prompt. Inviarne uno qualsiasi restituisce additional properties '<field>' not allowed. Se hai usato altri modelli hiapi — qwen-image-2.0 usa size, z-image usa aspect_ratio — nota che happyhorse-1.1/text-to-video appartiene alla famiglia delle proporzioni, non a quella delle dimensioni esplicite.
Una richiesta che utilizza tutti i parametri supportati:
curl -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "happyhorse-1.1/text-to-video",
"input": {
"prompt": "a vintage paper plane gliding above neon Tokyo at night, cinematic, 24fps",
"aspect_ratio": "16:9",
"resolution": "1080p",
"duration": 8
}
}'
Su costi e durata
Il modello fattura in base ai secondi di video generati — le tariffe attuali sono nella pagina dei prezzi. duration è la leva sui costi, resolution no. Passare da 5 a 15 secondi triplica il costo; al momento della stesura, passare da 720p a 1080p no. Durante lo sviluppo, usa per impostazione predefinita duration: 3 e una risoluzione inferiore; passa a 1080p solo sui prompt definitivi.
Ottenere il risultato: polling
Il modo più semplice per recuperare l'output è fare una GET del task per id finché status non raggiunge uno stato terminale.
import time
import urllib.request
import json
def get_task(task_id: str) -> dict:
req = urllib.request.Request(
f"https://api.hiapi.ai/v1/tasks/{task_id}",
headers={"Authorization": f"Bearer {API_KEY}"},
)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())["data"]
def wait_for_task(task_id: str, timeout_s: int = 600, poll_interval_s: int = 10) -> dict:
deadline = time.time() + timeout_s
while time.time() < deadline:
task = get_task(task_id)
status = task["status"]
if status == "success":
return task
if status == "fail":
raise RuntimeError(f"task failed: {task}")
time.sleep(poll_interval_s)
raise TimeoutError(f"task {task_id} still {status} after {timeout_s}s")
task = wait_for_task(submit_task("a quiet coffee shop on a rainy afternoon"))
video_url = task["output"][0]["url"]
print(video_url)
Alcuni dettagli importanti:
statusattraversa in sequenzaqueued → handling → archiving → success(ofail). Qualsiasi stato precedente asuccessofailnon deve essere considerato terminale.- Il tempo di generazione cresce con
duratione con la profondità attuale della coda. Un task da 5 secondi a 720p di solito si completa in pochi minuti; per 1080p e 15 secondi bisogna prevedere più tempo. output[].urlrisiede su storage temporaneo. Scarica subito i byte e ri-ospita il file autonomamente; non conservare solo l'URL per poi fornirlo agli utenti finali — scade.
import urllib.request
def download(video_url: str, out_path: str) -> None:
with urllib.request.urlopen(video_url) as resp, open(out_path, "wb") as f:
f.write(resp.read())
download(video_url, "ocean.mp4")
Ottenere il risultato: callback (modalità produzione)
Il polling va bene per script e notebook, ma in un backend reale non vuoi che un worker resti bloccato per minuti su ogni video. Passa un oggetto callback al momento dell'invio e hiapi invierà via POST il corpo del task completato al tuo URL esattamente una volta.
curl -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "happyhorse-1.1/text-to-video",
"input": {
"prompt": "an astronaut planting a flag on a glass beach",
"aspect_ratio": "16:9",
"resolution": "1080p",
"duration": 6
},
"callback": {
"url": "https://example.com/hooks/hiapi",
"when": "final"
}
}'
Al momento è supportato solo when: "final" — inviare altri valori restituisce:
{ "code": 400, "error_code": "INVALID_REQUEST",
"message": "invalid callback.when: only 'final' is supported" }
Progettare un ricevitore robusto
Tre regole rendono sicuro il percorso delle callback:
- Idempotenza. Persisti per
taskId. Se ricevi la stessa callback due volte — alla fine accadrà, a causa dei retry — l'elaborazione ripetuta deve essere un'operazione nulla. - Disaccoppia la conferma dall'elaborazione. Una volta accodato il payload, restituisci
200 OKdal webhook. Il download / il re-upload / la scrittura su database effettivi vanno eseguiti in un task in background. Un webhook lento provoca timeout e consegne duplicate. - Ripiego sul polling. Salva ogni
taskIdche invii nel tuo database insieme acreated_at. Esegui un processo di pulizia che interroga/v1/tasks/{id}per ogni riga che supera circa 30 minuti senza stato terminale. Le callback sono il percorso rapido, non l'unico percorso.
Un ricevitore FastAPI minimale:
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/hooks/hiapi")
async def hiapi_callback(req: Request):
body = await req.json()
task = body.get("data", body) # callback wraps the same shape as GET
task_id = task["taskId"]
if task["status"] == "success":
url = task["output"][0]["url"]
# enqueue, don't download here
await queue.put({"task_id": task_id, "url": url})
return {"ok": True}
Gli errori che incontrerai davvero
La struttura dell'envelope degli errori è sempre coerente — {code, error_code, message, data} — quindi puoi scrivere un solo handler. Ecco quelli che meritano una gestione speciale per questo modello:
| HTTP | error_code |
Condizione scatenante | Come risolvere |
|---|---|---|---|
| 400 | INVALID_REQUEST |
Campo sconosciuto, valore di enum errato, duration fuori dall'intervallo 3–15 |
Controlla message: indica il campo — ad esempio aspect_ratio: value must be one of '16:9'... |
| 401 | — | Chiave mancante o errata, oppure permission_denied |
Ricontrolla l'header Authorization: Bearer sk-... |
| 402 | — | Saldo insufficiente a coprire il costo stimato | Ricarica su hiapi.ai/en/pricing e riprova |
| 429 | — | Stai tempestando l'endpoint di richieste | Applica un backoff e riprova con jitter |
Un errore di validazione reale si presenta così — puoi copiarlo come fixture di test:
{
"code": 400,
"data": null,
"error_code": "INVALID_REQUEST",
"message": "invalid input: aspect_ratio: value must be one of '16:9', '9:16', '3:4', '4:3', '4:5', '5:4', '1:1', '9:21', '21:9'; resolution: value must be one of '720p', '1080p'; duration: maximum: got 99, want 15"
}
Nota che ogni campo viene segnalato nella stessa risposta. Valida localmente quando puoi, ma affidati a questo messaggio quando non puoi.
Contenuti correlati su hiapi
- Pagina del modello happyhorse-1.1/text-to-video — playground, UI completa dei parametri e prezzi in tempo reale
- happyhorse-1.1/image-to-video — stessa famiglia di modelli, ma anima un'immagine statica
- veo-3.1-fast/text-to-video e seedance-2.0 — modelli alternativi di testo a video sulla stessa API
/v1/tasks - Riferimento dell'API unificata dei task — fonte autorevole per
POST /v1/tasks,GET /v1/tasks/:id, callback ed envelope degli errori - Sfoglia tutti i modelli e prezzi
Domande frequenti
happyhorse-1.1/text-to-video genera audio?
Tramite l'API dei task no — la struttura della richiesta al momento non prevede un campo audio e inviarlo restituisce additional properties 'audio' not allowed. L'output è un MP4 muto. Aggiungi la colonna sonora a valle.
Quali proporzioni sono disponibili per verticale / short video?
Per output in stile TikTok e Reels, 9:16 è la scelta standard. L'API accetta anche 4:5 (verticale per il feed di Instagram) e 9:21 (verticale extra-lungo), se hai bisogno di un formato più stretto.
Qual è la durata minima e massima?
duration è un intero da 3 a 15 secondi (estremi inclusi). Inviando 0 vedrai duration: minimum: got 0, want 3; inviando 99 vedrai duration: maximum: got 99, want 15. Scegli il valore minimo che cattura il movimento desiderato — la durata è ciò che paghi.
Posso passare un seed per ottenere output riproducibili?
Il modello al momento non lo supporta. Richiedere {"input": {"prompt": "...", "seed": 42}} fallisce con additional properties 'seed' not allowed. Se la riproducibilità è importante, registra il corpo completo della richiesta e riprova con lo stesso prompt; otterrai risultati simili — ma non identici pixel per pixel.
È supportato negative_prompt?
No. Guida l'output aggiungendo vincoli direttamente nel prompt stesso (ad esempio "...clean composition, no text, no watermarks").
Quanto dura l'URL di output?
Con il piano predefinito il campo storage del task è temp e output[].url è un link firmato a validità breve. Scarica il file e ri-ospitalo non appena il task raggiunge success. Non salvare l'URL originale per fornirlo in seguito.
Come controllare i costi durante lo sviluppo?
Due abitudini per risparmiare: mantieni duration: 3 finché non hai bloccato i prompt e invia con resolution: "720p" finché la storyboard non è confermata. Passa a 1080p e a durate più lunghe solo per il render finale.
Polling o callback — quale usare?
Usa entrambi, in questo ordine. Prima il polling, perché ha un componente in meno da gestire. Passa alle callback la prima volta che hai un backend in cui non puoi permetterti di bloccare un worker su wait_for_task. In ogni caso mantieni il polling come processo di pulizia di ripiego; le callback sono best-effort, non consegna garantita.
Perché ricevo 402 pur avendo credito per chiamare? L'API stima il limite massimo del costo del task — durata × prezzo al secondo — e lo prenota in anticipo. Se il saldo non copre tale stima, restituisce subito 402. Ricarica, riprova e, in caso di successo, viene addebitato solo il costo effettivo.
Articoli correlati
Vedi tutti gli articoli →-
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
-
Tutoriale
Come chiamare hailuo-2.3/text-to-video tramite l'API HiAPI: curl, Python ed esempi di richiesta eseguibili
-
Tutoriale
Utilizzare Kandinsky 6.0 Pro su HiAPI per la generazione di video da testo: tutorial con curl e Python
Dimostrazione pratica guidata dalla richiesta: costruire la chiamata da prompt a clip video, inviare la richiesta, monitorare lo stato di completamento e scaricare il risultato