Tutoriel
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 :
statuspasse successivement parqueued → handling → archiving → success(oufail). Aucun état avantsuccessoufailne doit être considéré comme terminal.- Le temps de génération augmente avec
durationet 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[].urlest 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 :
- 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. - Découplez l'accusé de réception du traitement. Une fois la payload mise en file, renvoyez
200 OKdepuis 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. - Repli sur l'interrogation répétée. Stockez chaque
taskIdsoumis avec soncreated_atdans 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
- Page du modèle happyhorse-1.1/text-to-video — terrain de jeu, interface complète des paramètres et tarification en temps réel
- happyhorse-1.1/image-to-video — même famille de modèles, mais pour animer une image fixe
- veo-3.1-fast/text-to-video et seedance-2.0 — modèles texte-vers-vidéo alternatifs sur la même API
/v1/tasks - Référence de l'API de tâches unifiée — source d'autorité pour
POST /v1/tasks,GET /v1/tasks/:id, les callbacks et l'enveloppe d'erreur - Parcourir tous les modèles et tarification
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 →-
Tutoriel
Comment appeler hailuo-2.3-fast/image-to-video via l'API HiAPI : curl et Python en pratique
La première requête exploitable du palier image-vers-vidéo rapide et économique — de l'ID du modèle au téléchargement du MP4
-
Tutoriel
Comment appeler hailuo-2.3/text-to-video via l'API HiAPI : curl, Python et exemples de requêtes exécutables
-
Tutoriel
Utiliser Kandinsky 6.0 Pro texte-vers-vidéo sur HiAPI : tutoriel curl et Python
Démonstration pratique axée sur la requête : construire l'appel du prompt au clip vidéo, soumettre la requête, interroger l'état d'achèvement et télécharger le résultat