HiAPI
Blog

Tutoriale

API happyhorse 1.1 da testo a video: tutorial curl e Python funzionante

Jun 26, 2026
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:

  • status attraversa in sequenza queued → handling → archiving → success (o fail). Qualsiasi stato precedente a success o fail non deve essere considerato terminale.
  • Il tempo di generazione cresce con duration e 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[].url risiede 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:

  1. 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.
  2. Disaccoppia la conferma dall'elaborazione. Una volta accodato il payload, restituisci 200 OK dal 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.
  3. Ripiego sul polling. Salva ogni taskId che invii nel tuo database insieme a created_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

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.

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