HiAPI
Blog

Tutoriel

API texte-vers-vidéo Happyhorse 1.1 : tutoriel curl et Python exécutable

Jun 26, 2026
API texte-vers-vidéo Happyhorse 1.1 : tutoriel curl et Python exécutable

Cette page vous guide à travers la requête minimale pour générer une vidéo avec happyhorse-1.1/text-to-video, explique ce que chaque champ d'entrée accepte réellement, ainsi que les deux façons d'attendre le résultat (interrogation répétée et callback). Tout ce qui suit a été testé sur l'API en production ; les structures de requête et les messages d'erreur sont reproduits tels quels.

Ce que vous allez construire

Une fonction : elle reçoit un prompt texte et renvoie une URL MP4 téléchargeable. Le modèle est happyhorse-1.1/text-to-video, exposé via le point de terminaison unifié /v1/tasks. Vous soumettez une tâche, récupérez immédiatement un taskId, puis soit vous interrogez le statut jusqu'à la fin, soit vous laissez hiapi envoyer le résultat final en POST vers votre webhook.

Prérequis :

  • Une clé API hiapi. Les nouveaux comptes reçoivent 1 $ de crédit gratuit — à récupérer sur la page API Keys.
  • Un moyen d'envoyer des requêtes HTTPS. Aucun SDK à installer.

Ne placez pas la clé dans du code client et ne la commitez pas dans git. Lisez-la depuis une variable d'environnement :

export HIAPI_API_KEY="sk-..."

Requête minimale fonctionnelle

Le seul champ d'entrée obligatoire est prompt. Tous les autres ont une valeur par défaut.

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": "une vague océanique calme au coucher du soleil, ralenti, heure dorée"
    }
  }'

Réponse :

{
  "code": 200,
  "data": { "taskId": "tk-hiapi-01KW23VDG2EZ20J7RQH8S67MNC" },
  "message": "success"
}

Ce taskId est votre justificatif pour manipuler cette tâche. Conservez-le soigneusement.

Python (bibliothèque standard uniquement)

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("une vague océanique calme au coucher du soleil, ralenti, heure dorée")
print(task_id)  # e.g. tk-hiapi-01KW23VDG2EZ20J7RQH8S67MNC

Tableau complet des paramètres

La structure des paramètres est petite et stricte — les champs inconnus sont rejetés avec une 400, et non silencieusement ignorés. Voici les champs réellement acceptés par l'API dans input, directement issus des messages d'erreur de validation :

Champ Type Obligatoire Valeurs autorisées Comportement par défaut
prompt string Oui Tout texte non vide décrivant la scène —
aspect_ratio string Non 16:9, 9:16, 3:4, 4:3, 4:5, 5:4, 1:1, 9:21, 21:9 Valeur par défaut du modèle
resolution string Non 720p, 1080p Valeur par défaut du modèle
duration integer Non 3 à 15 (secondes) Valeur par défaut du modèle

Champs qui semblent aller de soi mais qui ne sont actuellement pas acceptés : size, audio, seed, negative_prompt. L'envoi de l'un d'entre eux renvoie additional properties '<field>' not allowed. Si vous avez déjà utilisé d'autres modèles hiapi — qwen-image-2.0 utilise size, z-image utilise aspect_ratio — notez que happyhorse-1.1/text-to-video appartient à la famille des ratios d'image, et non à celle des dimensions explicites.

Une requête utilisant tous les paramètres pris en charge :

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": "un avion en papier vintage planant au-dessus de Tokyo néon la nuit, cinématographique, 24fps",
      "aspect_ratio": "16:9",
      "resolution": "1080p",
      "duration": 8
    }
  }'

À propos du coût et de la durée

Ce modèle est facturé au nombre de secondes de vidéo générées — voir le tarif actuel sur la page de tarification. duration est le levier de coût, resolution ne l'est pas. Passer de 5 à 15 secondes triple la facture ; au moment de la rédaction, passer de 720p à 1080p ne le fait pas. Pendant le développement, utilisez par défaut duration: 3 et une résolution plus basse ; ne passez à 1080p que sur les prompts définitivement validés.

Obtenir le résultat : interrogation répétée

La façon la plus simple de récupérer la sortie est de faire un GET sur la tâche par id jusqu'à ce que status atteigne un état terminal.

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("un café tranquille un après-midi pluvieux"))
video_url = task["output"][0]["url"]
print(video_url)

Quelques détails importants :

  • status passe successivement par queued → handling → archiving → success (ou fail). Aucun état avant success ou fail ne doit être considéré comme terminal.
  • Le temps de génération augmente avec duration et la profondeur actuelle de la file. Une tâche de 5 secondes en 720p se termine généralement en quelques minutes ; pour du 1080p de 15 secondes, prévoyez plus de temps.
  • output[].url est stocké dans un stockage temporaire. Téléchargez immédiatement les octets et réhébergez-les vous-même ; ne vous contentez pas de conserver l'URL pour la fournir aux utilisateurs finaux — elle expirera.
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")

Obtenir le résultat : callback (mode production)

L'interrogation répétée convient aux scripts et aux notebooks, mais dans un vrai backend, vous ne voulez pas qu'un worker soit bloqué plusieurs minutes par vidéo. Passez un objet callback à la soumission, et hiapi enverra en POST le corps de la tâche terminée exactement une fois vers votre URL.

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": "un astronaute plantant un drapeau sur une plage de verre",
      "aspect_ratio": "16:9",
      "resolution": "1080p",
      "duration": 6
    },
    "callback": {
      "url": "https://example.com/hooks/hiapi",
      "when": "final"
    }
  }'

Seul when: "final" est actuellement pris en charge — l'envoi d'une autre valeur renvoie :

{ "code": 400, "error_code": "INVALID_REQUEST",
  "message": "invalid callback.when: only 'final' is supported" }

Concevoir un récepteur robuste

Trois règles garantissent la sécurité du chemin de callback :

  1. Idempotence. Persistez par taskId. Si vous recevez deux fois le même callback — ce qui finira par arriver, à cause des retentatives — le traitement en double doit être une opération sans effet.
  2. Découplez l'accusé de réception du traitement. Une fois la payload mise en file, renvoyez 200 OK depuis le webhook. Le téléchargement / le réhébergement / l'écriture en base se font dans une tâche d'arrière-plan. Un webhook lent attire les timeouts et les livraisons en double.
  3. Repli sur l'interrogation répétée. Stockez chaque taskId soumis avec son created_at dans votre propre base de données. Faites tourner un balayeur qui interroge /v1/tasks/{id} pour toute ligne dépassant environ 30 minutes sans état terminal. Le callback est le chemin rapide, pas le seul chemin.

Un récepteur FastAPI minimal :

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}

Les erreurs que vous rencontrerez réellement

La forme de l'enveloppe d'erreur est toujours cohérente — {code, error_code, message, data} — vous pouvez donc écrire un seul gestionnaire. Voici celles qui méritent un traitement particulier pour ce modèle :

HTTP error_code Condition de déclenchement Méthode de correction
400 INVALID_REQUEST Champ inconnu, valeur d'énumération erronée, duration hors de 3–15 Consultez message, il indique le champ — par exemple aspect_ratio: value must be one of '16:9'...
401 — Clé manquante ou erronée, ou permission_denied Vérifiez à nouveau l'en-tête Authorization: Bearer sk-...
402 — Solde insuffisant pour couvrir le coût estimé Rechargez sur hiapi.ai/en/pricing puis réessayez
429 — Vous bombardez le point de terminaison Utilisez un backoff avec jitter pour les retentatives

Voici à quoi ressemble une véritable échec de validation — copiez-la comme fixture de 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"
}

Remarquez que chaque champ est signalé dans la même réponse. Validez localement quand vous le pouvez, mais appuyez-vous dessus quand ce n'est pas possible.

Contenus connexes sur hiapi

FAQ

happyhorse-1.1/text-to-video génère-t-il de l'audio ? Pas via l'API de tâches — la structure de requête n'a actuellement pas de champ audio, et l'envoyer renvoie additional properties 'audio' not allowed. La sortie est un MP4 muet. Ajoutez une bande-son en aval.

Quels ratios d'image utiliser pour le portrait / les vidéos courtes ? Pour les sorties de style TikTok et Reels, 9:16 est le choix standard. L'API accepte aussi 4:5 (portrait pour le fil Instagram) et 9:21 (portrait extra-long), si vous avez besoin d'un cadre plus étroit.

Quelle est la durée minimale et maximale ? duration est un entier de 3 à 15 secondes (bornes incluses). Envoyer 0 donne duration: minimum: got 0, want 3 ; envoyer 99 donne duration: maximum: got 99, want 15. Choisissez la plus petite valeur qui capture le mouvement souhaité — la durée est ce que vous payez.

Peut-on passer seed pour obtenir des sorties reproductibles ? Ce modèle ne le prend pas en charge actuellement. La requête {"input": {"prompt": "...", "seed": 42}} échoue avec additional properties 'seed' not allowed. Si la reproductibilité est importante, enregistrez le corps complet de la requête et réessayez avec le même prompt ; vous obtiendrez des résultats similaires — mais pas identiques au pixel près —.

negative_prompt est-il pris en charge ? Non. Guidez la sortie en ajoutant des contraintes directement dans prompt (par exemple "...clean composition, no text, no watermarks").

Combien de temps l'URL de sortie reste-t-elle valide ? Avec le forfait par défaut, le champ storage de la tâche vaut temp, et output[].url est un lien signé à durée de vie courte. Téléchargez le fichier et réhébergez-le dès que la tâche atteint success. Ne conservez pas l'URL d'origine pour la fournir plus tard.

Comment maîtriser les coûts pendant le développement ? Deux habitudes économes : gardez duration: 3 jusqu'à ce que le prompt soit verrouillé, et soumettez en resolution: "720p" jusqu'à validation du storyboard. Ne passez à 1080p et à des durées plus longues que pour le rendu final.

Interrogation répétée ou callback — lequel utiliser ? Utilisez les deux, dans cet ordre. D'abord l'interrogation répétée, car elle a un composant mobile de moins. Passez au callback dès que vous avez pour la première fois un backend qui ne peut pas se permettre de bloquer un worker sur wait_for_task. Dans tous les cas, conservez l'interrogation répétée comme balayeur de repli ; le callback est au mieux un effort, pas une garantie de livraison.

Pourquoi une 402 alors que j'ai du crédit pour appeler ? L'API estime la borne supérieure du coût de la tâche — durée × prix par seconde — et la réserve à l'avance. Si le solde ne couvre pas cette estimation, elle renvoie immédiatement une 402. Rechargez, réessayez, et en cas de succès seul le coût réel est débité.

Articles similaires

Voir tous les articles →

Commencez avec des artefacts IA persistants

Obtenez votre clé API et appelez des modèles d'image, de vidéo et d'audio via une API unique basée sur les tasks.

Lire la documentation de l'API