HiAPI
Blog

Tutoriale

API video da riferimento HappyHorse 1.1: tutorial curl + Python pronto all'uso

Jun 26, 2026
API video da riferimento HappyHorse 1.1: tutorial curl + Python pronto all'uso

HappyHorse 1.1 R2V trasforma fino a nove immagini di riferimento più un prompt testuale in un breve video, mantenendo coerenti soggetto, abbigliamento e stile della scena tra le varie inquadrature. Su HiAPI il modello è disponibile tramite l'endpoint unificato per attività asincrone, con ID modello happyhorse-1.1/reference-to-video. Questo tutorial mostra prima la richiesta minima funzionante, poi una versione di livello produttivo con callback.

Obiettivi e prerequisiti

Completerai le seguenti operazioni:

  1. Carica (o ospita) da 1 a 9 immagini di riferimento su URL HTTPS accessibili all'API.
  2. Invia con POST a https://api.hiapi.ai/v1/tasks un'attività che fa riferimento a questi URL.
  3. Quando l'attività raggiunge lo stato success, leggi l'indirizzo MP4 finale da data.output[0].url.

Prerequisiti:

  • Un account HiAPI e una chiave API che inizia con sk-. Puoi ottenerla dalla console HiAPI e consultare la documentazione sull'autenticazione per le regole degli header. La chiave deve essere trasmessa come Authorization: Bearer sk-... e non va mai inserita nel codice lato client.
  • Requisiti di hosting delle immagini: ogni immagine di riferimento deve essere in formato JPEG, PNG o WEBP, con lato più corto ≥ 400px e dimensioni ≤ 20MB. Gli URL CDN pubblici vanno bene; anche gli URL S3 firmati sono validi, purché l'esecutore dell'attività possa accedervi.

Richiesta minima funzionante (curl)

Questo è il corpo della richiesta più piccolo accettato dallo schema. Usa l'output predefinito 1080p / 16:9 / 5 secondi, quindi devi impostare solo i campi prompt e reference_image.

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "happyhorse-1.1/reference-to-video",
    "input": {
      "prompt": "Il personaggio in [Image 1] cammina in una scena in stile [Image 2], inquadratura cinematografica",
      "reference_image": [
        "https://static.hiapi.ai/example/ref-1.jpg",
        "https://static.hiapi.ai/example/ref-2.jpg"
      ]
    }
  }'

La risposta è la busta dell'attività; l'unico campo che ti serve davvero è data.taskId:

{
  "code": 200,
  "data": { "taskId": "task_xxx", "status": "queued" }
}

Esegui polling fino allo stato terminale:

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

Quando data.status diventa success, l'URL del video si trova in data.output[0].url. Tale URL include un timestamp expireAt, quindi scarica subito il file oppure trasferiscilo su uno storage persistente: non collegare direttamente quell'indirizzo sul tuo sito.

Parametri

L'endpoint R2V convalida rigorosamente l'input; i campi sconosciuti restituiscono 400 INVALID_REQUEST. Schema completo (verificato in tempo reale per POST /v1/tasks):

Campo Tipo Obbligatorio Valori ammessi Valore predefinito
model string Sì happyhorse-1.1/reference-to-video (valore fisso) -
input.prompt string Sì Usa [Image 1], [Image 2]… per fare riferimento alle immagini, nello stesso ordine di reference_image -
input.reference_image string[] Sì Da 1 a 9 URL HTTPS, formato JPEG/PNG/WEBP, lato più corto ≥ 400px, ≤ 20MB ciascuna -
input.resolution enum No 720p, 1080p 1080p
input.aspect_ratio enum No 16:9, 9:16, 3:4, 4:3, 4:5, 5:4, 1:1, 9:21, 21:9 16:9
input.duration integer No 3-15 (secondi) 5
callback.url string No URL HTTPS che riceve gli eventi di stato terminale dell'attività -
callback.when enum No final (attualmente l'unico valore) final

Ecco alcune regole di convalida poco intuitive che vale la pena conoscere prima che ti causino problemi:

  • Anche con una sola immagine di riferimento, passa reference_image come array. Passare direttamente una stringa restituisce reference_image: got string, want array.
  • duration deve essere un intero compreso tra 3 e 15 inclusi. 999 e 1 restituiscono entrambi un messaggio di errore esplicito di superamento dei limiti.
  • A livello superiore non esistono seed, negative_prompt, fps, cfg_scale o steps. L'API rifiuta le proprietà sconosciute.

Per il prezzo completo in base alla combinazione di risoluzione e durata, consulta la pagina dei prezzi HiAPI: il costo varia in base a entrambi i parametri resolution e duration.

Python: end-to-end + polling

Il blocco di codice seguente è un generatore autonomo che invia l'attività, esegue polling fino allo stato terminale e scarica il video. È volutamente privo di dipendenze (usa solo requests), così puoi copiarlo in qualsiasi servizio. Per una descrizione più completa del ciclo di vita delle richieste, consulta la guida rapida.

import os
import time
import requests

API = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"}


def submit_r2v(prompt: str, reference_images: list[str], duration: int = 5,
               resolution: str = "1080p", aspect_ratio: str = "16:9") -> str:
    if not 1 <= len(reference_images) <= 9:
        raise ValueError("reference_image must contain 1-9 URLs")
    payload = {
        "model": "happyhorse-1.1/reference-to-video",
        "input": {
            "prompt": prompt,
            "reference_image": reference_images,
            "resolution": resolution,
            "aspect_ratio": aspect_ratio,
            "duration": duration,
        },
    }
    r = requests.post(API, json=payload, headers=HEADERS, timeout=60)
    r.raise_for_status()
    data = r.json()
    if data.get("code") != 200:
        raise RuntimeError(f"create failed: {data}")
    return data["data"]["taskId"]


def wait_for_video(task_id: str, timeout_s: int = 600, poll_s: int = 5) -> str:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(f"{API}/{task_id}", headers=HEADERS, timeout=30)
        r.raise_for_status()
        task = r.json()["data"]
        status = task["status"]
        if status == "success":
            return task["output"][0]["url"]
        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} did not finish in {timeout_s}s")


def download(url: str, path: str) -> None:
    with requests.get(url, stream=True, timeout=300) as r:
        r.raise_for_status()
        with open(path, "wb") as f:
            for chunk in r.iter_content(1 << 16):
                f.write(chunk)


if __name__ == "__main__":
    task_id = submit_r2v(
        prompt="Mantieni il design del soggetto in [Image 1], mentre cammina nell'atmosfera di [Image 2]",
        reference_images=[
            "https://static.hiapi.ai/example/char.jpg",
            "https://static.hiapi.ai/example/mood.jpg",
        ],
        duration=5,
        resolution="720p",
        aspect_ratio="9:16",
    )
    print("submitted", task_id)
    video_url = wait_for_video(task_id)
    print("ready", video_url)
    download(video_url, "out.mp4")

Questo codice fa intenzionalmente quanto segue:

  • Legge la chiave dalla variabile d'ambiente HIAPI_API_KEY, così non finisce mai in git o nei log dei container.
  • Usa r.raise_for_status() per intercettare gli errori di trasporto prima di analizzare il JSON.
  • Considera stati terminali solo success e fail: tutti gli altri stati (queued, processing, ecc.) fanno continuare il ciclo.
  • Scarica subito l'URL di output. Gli URL di output sono indirizzi firmati e scadono; se ti serve il file più tardi, trasferiscilo su uno storage persistente o copialo nel tuo bucket.

In produzione: passare dal polling al webhook

Il polling ogni 5 secondi va bene per un notebook o un worker a basso traffico. Per un servizio reale, registra un callback al momento della creazione dell'attività, così HiAPI invia gli eventi di stato terminale al tuo endpoint: i tuoi worker restano inattivi e non paghi più per richieste GET a vuoto. Per lo schema completo della richiesta consulta Creare un'attività, mentre per il polling di riserva consulta Ottenere i dettagli dell'attività.

payload = {
    "model": "happyhorse-1.1/reference-to-video",
    "input": {
        "prompt": "Il personaggio in [Image 1] attraversa la strada cittadina di [Image 2]",
        "reference_image": [
            "https://cdn.example.com/char.jpg",
            "https://cdn.example.com/street.jpg",
        ],
        "resolution": "1080p",
        "aspect_ratio": "16:9",
        "duration": 8,
    },
    "callback": {
        "url": "https://your-domain.com/hiapi/callback",
        "when": "final",
    },
}

L'endpoint di ricezione deve gestire due aspetti:

  1. Idempotenza per taskId. Sia success che fail sono stati terminali; i tentativi ripetuti di HiAPI (o i duplicati generati dal tuo livello di retry) consegnano lo stesso taskId. Deduplica prima di attivare qualsiasi effetto collaterale (copia su S3, messaggio Slack, evento di fatturazione).
  2. Polling di verifica di riserva. Se il tuo endpoint è inattivo, il callback va perso. Mantieni un'attività di verifica leggera che chiami GET /v1/tasks/<id> per tutte le attività in stato processing da più del tempo di generazione nel caso peggiore.

Errori che incontrerai realmente

Questi meritano una gestione esplicita; tutti gli altri sono 5xx temporanei, che l'SDK o il tuo client HTTP dovrebbero ritentare con backoff.

  • 401 permission_denied: header Authorization mancante, malformato o chiave revocata. Richiedi una nuova chiave dalla console invece di modificare l'header.
  • 400 INVALID_REQUEST con percorso del campo: il messaggio di errore ti dice esattamente dov'è fallito, ad esempio reference_image: maxItems: got 10, want 9 oppure aspect_ratio: value must be one of .... Convalida l'input prima che il worker invii la richiesta, per fallire rapidamente in locale.
  • task failed (stato terminale): il polling/callback restituisce status: "fail" con error.code e error.message. Cause comuni: URL di immagini di riferimento che il worker non riesce a recuperare, attivazione della moderazione NSFW o lato più corto dell'immagine inferiore alla soglia di 400px.

Domande frequenti

Si possono referenziare più di 9 immagini? No. Lo schema limita reference_image a un massimo di 9. Se ti servono più variazioni, componi fuori dalla piattaforma un'unica immagine di riferimento più densa (una griglia o uno storyboard) e usala come singolo input.

È obbligatorio usare i marcatori [Image N] nel prompt? Questi marcatori sono il modo in cui indichi immagini specifiche. Se il prompt non specifica mai le immagini è comunque valido, ma il modello mescolerà liberamente tutte le immagini di riferimento: per scenari con più inquadrature coerenti, di solito non è quello che vuoi.

Perché ricevo 503 nsfw_moderation_unavailable? È un errore temporaneo della piattaforma causato dal servizio di moderazione, indipendente dalla tua richiesta. Riprova dopo un breve backoff.

Meglio 720p o 1080p? 1080p è il valore predefinito e offre una qualità migliore, ma costa di più e richiede più tempo di rendering. Per clip verticali da social che i player riducono comunque di risoluzione, 720p con aspect_ratio: 9:16 è di solito un compromesso più adeguato.

Si può passare seed per rendere l'output riproducibile? Al momento no. L'API considera seed una proprietà sconosciuta e la rifiuta. Se ti serve riproducibilità, fissa esattamente prompt, reference_image, aspect_ratio, resolution e duration e accetta una certa variabilità tra un'esecuzione e l'altra.

Dove vengono conservati gli URL di output e per quanto tempo sono validi? Sono URL firmati con TTL breve; la risposta include expireAt. Dopo il successo dell'attività, scarica subito il file oppure trasferiscilo su uno storage persistente.

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