Tutoriel
Comment utiliser l'API Ray 3.2 image-to-video : curl, Python et une requête qui fonctionne du premier coup
Ray 3.2 image-to-video anime une image fixe : vous lui fournissez une image plus une invite décrivant le mouvement, et il renvoie une courte vidéo qui démarre à partir de votre image. Sur hiapi, l'id du modèle est ray-3.2/image-to-video, appelé via la même API de tâches asynchrones unifiée que tous les autres modèles vidéo — POST /v1/tasks, puis interrogation répétée ou réception d'un callback.
C'est un point de terminaison différent de Ray 3.2 text-to-video, et pas seulement une invite différente. Son schéma d'entrée est plus petit et plus strict, et deux contrôles auxquels vous êtes peut-être habitué avec le modèle texte ont disparu. Ce guide vous donne une requête qui fonctionne du premier coup : le schéma exact (vérifié contre l'API en ligne le 2026-10-20), un curl directement copiable, un script Python complet de soumission/interrogation/téléchargement, ainsi que les détails de production — callbacks, idempotence, et les vraies réponses d'erreur, y compris ce mode de défaillance qui n'apparaît pas là où vous l'attendez.
Ce que vous allez construire, et ce qu'il vous faut
Objectif : soumettre une image de première image + une invite de mouvement, interroger la tâche, puis télécharger un .mp4 muet qui anime votre image.
Prérequis :
- Une clé API hiapi — obtenez-en une depuis votre tableau de bord. La clé ressemble à
sk-...et se place dans l'en-têteAuthorization: Bearer. Nouveau sur l'API de tâches ? Consultez d'abord le guide de démarrage rapide. - Une URL d'image accessible publiquement pour la première image (voir ci-dessous). N'importe quel client HTTP — nous montrerons
curlet Python (requests).
Ray 3.2 image-to-video est facturé au clip vidéo. Comme la durée du clip est fixe (voir ci-dessous), resolution est votre seul levier de coût — un clip 1080p coûte plusieurs fois le prix d'un clip 540p. Consultez les tarifs actuels, et prototypez avec 540p pendant que vous itérez sur l'invite.
Le schéma d'entrée exact (deux champs obligatoires, un optionnel)
Tout se place dans l'objet input. Le schéma est strict — il rejette tout champ non reconnu. Vérifié champ par champ contre l'API en ligne :
| Champ | Type | Obligatoire | Valeurs autorisées | Valeur par défaut |
|---|---|---|---|---|
prompt |
string | oui | comment l'image doit bouger (caméra, mouvement du sujet) | — |
image_url |
string | oui | URL publique de la première image | — |
resolution |
string | non | 540p, 720p, 1080p |
540p |
Trois pièges dans lesquels tombent facilement ceux qui viennent du modèle text-to-video :
- Pas de
duration. La durée du clip est fixe (environ 5 secondes). Envoyerdurationne sera pas tronqué — c'est directement rejeté. C'est l'inverse de Ray 3.2 text-to-video, oùdurationest un entier optionnel (5 ou 10). - Pas d'
aspect_ratio. Le cadrage de sortie suit votre image d'entrée, il n'y a donc rien à définir. Avec le modèle texte vous choisissez le ratio ; ici c'est l'image qui décide. (Confirmé ci-dessous par une exécution réelle : une entrée carrée a produit un clip carré.) - Le schéma est fermé.
seed,negative_prompt,prompt_optimizeret tout autre champ retourneront un 400 avecadditional properties '<field>' not allowed. Ne transplantez pas des corps de requête d'autres modèles en supposant que les clés superflues seront ignorées.
Ce que la validation renvoie réellement
Voici de vraies réponses du point de terminaison en ligne — valeurs invalides soumises volontairement, aucune tâche créée, aucune facturation :
// POST with no image_url
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
"message":"invalid input: image_url: missing required field \"image_url\""}
// POST with "resolution": "2k"
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
"message":"invalid input: resolution: value must be one of '540p', '720p', '1080p'"}
// POST with a "duration" field
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
"message":"invalid input: <root>: additional properties 'duration' not allowed"}
// POST with an "aspect_ratio" field
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
"message":"invalid input: <root>: additional properties 'aspect_ratio' not allowed"}
Le message pointe le champ fautif précis, donc l'API est elle-même votre référence de schéma quand vous oubliez quelque chose.
Préparer la première image
image_url doit être une URL que le worker hiapi peut récupérer — un lien public, pas un chemin local, pas un blob base64 data:. L'image détermine le ratio de la vidéo et la composition d'ouverture, donc partez de l'image exacte où vous voulez que le mouvement commence.
Un piège à signaler d'avance, car il est facile à manquer : une mauvaise image_url n'est pas capturée au moment de la création de la tâche. L'appel de création ne valide que la forme de la requête (est-ce une chaîne ?), pas si l'URL est résolvable. Une URL d'image inaccessible ou 404 renverra un 200 parfaitement normal et un taskId — puis la tâche basculera en fail quelques secondes plus tard. Nous verrons la charge de défaillance exacte dans la section dépannage ; l'essentiel est de toujours brancher sur le status terminal de la tâche, et de ne jamais supposer qu'une création 200 signifie un succès.
Exemple minimal fonctionnel : curl
Créer la tâche :
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "ray-3.2/image-to-video",
"input": {
"prompt": "slow cinematic push-in, gentle parallax, soft drifting light, the subject breathing almost imperceptibly",
"image_url": "https://your-cdn.example.com/first-frame.jpg",
"resolution": "540p"
}
}'
La réponse de création porte un id de tâche :
{ "code": 200, "data": { "taskId": "tk-hiapi-01M5..." }, "message": "success" }
Interrogez jusqu'à l'atteinte d'un état terminal :
curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-01M5... \
-H "Authorization: Bearer sk-YOUR_KEY"
Pendant l'exécution de la tâche, data.status passe par plusieurs états en cours (vous verrez des valeurs comme handling, puis archiving). Les deux états terminaux sont success et fail. En cas de succès :
{
"code": 200,
"data": {
"taskId": "tk-hiapi-01M5...",
"status": "success",
"created": 1792435735,
"completed": 1792435781,
"storage": "temp",
"output": [
{ "type": "video", "artifactId": "278504",
"expireAt": 1793040580,
"url": "https://temp.hiapi.ai/.../clip.mp4" }
]
},
"message": "success"
}
Téléchargez immédiatement data.output[0].url. C'est une URL signée sur temp.hiapi.ai avec un expireAt (horodatage Unix, environ une semaine plus tard) — persistez les octets dans votre propre stockage plutôt que de créer un lien direct. Ray 3.2 renvoie un MP4 muet ; il n'y a aucune piste audio à attendre.
Script Python complet (soumission → interrogation → téléchargement)
import time
import requests
API_BASE = "https://api.hiapi.ai/v1"
API_KEY = "sk-YOUR_KEY"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
def create_task(image_url: str, prompt: str, resolution: str = "540p") -> str:
resp = requests.post(
f"{API_BASE}/tasks",
headers=HEADERS,
json={
"model": "ray-3.2/image-to-video",
"input": {
"prompt": prompt,
"image_url": image_url, # public URL of the first frame (required)
"resolution": resolution, # "540p" | "720p" | "1080p"
# no "duration" and no "aspect_ratio" — both are rejected
},
},
timeout=30,
)
resp.raise_for_status()
return resp.json()["data"]["taskId"]
def wait_task(task_id: str, timeout_s: int = 900, poll_interval: int = 8) -> dict:
"""Poll until terminal. Tolerates transient network blips by re-polling
the SAME saved task_id instead of aborting the whole job."""
deadline = time.time() + timeout_s
while time.time() < deadline:
try:
resp = requests.get(f"{API_BASE}/tasks/{task_id}", headers=HEADERS, timeout=30)
resp.raise_for_status()
except requests.RequestException:
time.sleep(poll_interval) # a dropped poll is not a failed task
continue
task = resp.json()["data"]
if task["status"] == "success":
return task
if task["status"] == "fail":
err = task.get("error") or {}
raise RuntimeError(f"task failed: {err.get('code')}: {err.get('message')}")
time.sleep(poll_interval) # any non-terminal status = still running
raise TimeoutError(f"task {task_id} not finished after {timeout_s}s")
task_id = create_task(
image_url="https://your-cdn.example.com/first-frame.jpg",
prompt="slow cinematic push-in, gentle parallax, soft drifting light",
)
print("task created:", task_id)
task = wait_task(task_id)
video_url = task["output"][0]["url"]
# Output URLs expire — download right away and store the bytes yourself.
video = requests.get(video_url, timeout=120).content
with open("ray32-i2v.mp4", "wb") as f:
f.write(video)
print(f"saved ray32-i2v.mp4 ({len(video) / 1e6:.1f} MB)")
Exécutez-le avec pip install requests, remplacez votre clé et une vraie image_url, et une fois la tâche terminée vous aurez un .mp4 sur votre disque.
À quoi ressemble une exécution réelle
Pour concrétiser les chiffres, voici une vraie exécution 540p contre l'API en ligne le 2026-10-20. Première image : une image 1600×1600 (carrée) ; invite : un lent travelling avant cinématographique.
- La tâche a traversé
handling → archiving → success, le travail côté serveur s'achevant en environ 45 secondes (createdcréation,completedachèvement) — soit environ 60 secondes réelles avec la boucle d'interrogation de 8 secondes. Les tâches vidéo prennent des dizaines de secondes à quelques minutes, pas un instant ; prévoyez des délais généreux. - La sortie était un MP4 720×720, environ 1,3 Mo, 5,04 secondes, muet.
Cette exécution vous apprend deux choses que le tableau des paramètres ne dit pas :
- Le ratio de sortie correspond à l'entrée — carré en entrée, carré en sortie. C'est pourquoi il n'y a pas de champ
aspect_ratio; cadrez votreimage_urlselon la composition de clip que vous souhaitez. resolutionest un palier de qualité, pas une hauteur littérale en pixels. En demandant540ppour une entrée 1600×1600, on obtient du 720×720 — ce palier s'adapte à la forme de l'entrée au lieu d'imposer une taille fixe. Si vous avez besoin de dimensions de sortie exactes, redimensionnez vous-même le clip livré.
Considérations de production : callbacks, idempotence, erreurs
Préférez les callbacks à l'interrogation
Pour tout ce qui sort d'un script, enregistrez un webhook à la création plutôt que d'interroger :
{
"model": "ray-3.2/image-to-video",
"input": {
"prompt": "...",
"image_url": "https://your-cdn.example.com/first-frame.jpg",
"resolution": "720p"
},
"callback": { "url": "https://your-app.example.com/hooks/hiapi", "when": "final" }
}
Avec "when": "final", hiapi appelle votre point de terminaison une fois lorsque la tâche atteint un état terminal — pas de trafic d'interrogation, et pas de fin ratée entre deux intervalles. Votre handler exécute ensuite GET /v1/tasks/<id> pour récupérer output[0].url. Pour les CLI et notebooks qui ne peuvent pas exposer de point de terminaison HTTPS, l'interrogation reste très bien.
Rendez les réessais idempotents
Persistez le taskId dès le retour de l'appel de création. Si votre processus plante en cours de production, reprenez en interrogeant l'id stocké — ne recréez pas la tâche, sinon vous paierez un second clip. Ne recréez que si la requête de création initiale a échoué avant de renvoyer un id de tâche. La boucle d'interrogation tolérante ci-dessus suit déjà cette règle : en cas de coupure réseau, réinterroger le même id au lieu de repartir de zéro.
Les erreurs que vous verrez réellement
Chaque ligne du tableau ci-dessous est une forme de réponse en ligne vérifiée :
| Symptôme | Réponse | Correctif |
|---|---|---|
| Clé manquante/invalide | 401, imbriqué {"error":{"code":"permission_denied","type":"hiapi_error","request_id":"...","message":"This API key is invalid..."}} |
Vérifiez l'en-tête Authorization: Bearer sk-... et que la clé a les droits d'accès. Fournissez request_id au support. |
image_url manquant |
400 INVALID_REQUEST, message: "image_url: missing required field" |
Ce modèle exige image_url — pas de mode texte seul. |
| Mauvaise énumération / champ superflu | 400 INVALID_REQUEST, plat {"code":400,"error_code":"INVALID_REQUEST","message":"..."} |
Lisez le champ indiqué dans message. Pour duration/aspect_ratio, il dira additional properties ... not allowed — supprimez-les. |
| Mauvais id de tâche lors de l'interrogation | 404, plat {"code":404,"data":null,"message":"task not found"} (note : pas d'error_code sur les 404) |
Id erroné, ou vous interrogez avec la clé d'un autre compte. |
image_url inaccessible |
La création renvoie 200 + un taskId ; la tâche se termine ensuite en data.status:"fail" avec data.error:{"code":"INVALID_REQUEST","message":"Invalid request parameters for this model..."} |
C'est le piège. L'appel de création ne peut pas distinguer un lien mort d'un lien vivant. Vérifiez que votre image est accessible publiquement et lisez toujours le data.status terminal. |
La dernière ligne mérite d'être retenue : même quand la tâche échoue, l'enveloppe HTTP de l'interrogation reste 200 — l'échec réside dans data.status et data.error, pas dans le code de statut HTTP. Les tâches échouées sont remboursées, mais votre code doit quand même les remarquer. Branchez sur data.status == "fail", ne croyez pas le 200.
Ressources connexes
- Page du modèle Ray 3.2 image-to-video — playground, état en temps réel et tableau de prix complet par résolution
- Page du modèle Ray 3.2 text-to-video — le point de terminaison frère ; partez du texte plutôt que de l'image (il ajoute
durationetaspect_ratio, retireimage_url) - Comment utiliser l'API Hailuo 2.3 image-to-video — un autre tutoriel image-to-video sur la même API de tâches, utile pour comparer les schémas
- Comment utiliser l'API grok-imagine image-to-video — un troisième modèle i2v pour comparaison
- Tarifs hiapi — prix vidéo par clip selon la résolution
FAQ
Quel est l'id du modèle, et quel point de terminaison appeler ?
ray-3.2/image-to-video, soumis à POST https://api.hiapi.ai/v1/tasks, avec les paramètres sous input. C'est asynchrone — vous récupérez un taskId, puis interrogez GET /v1/tasks/<id> (ou enregistrez un callback).
Que faut-il en entrée ?
Exactement deux champs obligatoires — prompt (mouvement) et image_url (URL publique de la première image) — plus resolution optionnel (540p/720p/1080p). Tout le reste sera rejeté.
Pourquoi ne puis-je pas définir la durée ou le ratio ?
La durée du clip est fixe (environ 5 secondes), d'où l'absence de champ duration ; le ratio de sortie hérite de votre image d'entrée, d'où l'absence de champ aspect_ratio. Les envoyer retourne 400. Si vous avez besoin d'une durée sélectionnable, utilisez plutôt Ray 3.2 text-to-video.
Mon appel de création a renvoyé 200, mais la vidéo n'arrive jamais. Pourquoi ?
Le plus souvent, c'est une image_url inaccessible. L'appel de création ne valide que la forme de la requête, donc un lien mort renvoie quand même un taskId — puis la tâche finit en fail. Interrogez jusqu'à un état terminal et lisez data.error. Assurez-vous que l'image est récupérable publiquement (sans authentification, pas de chemin local, pas de base64).
Le clip a-t-il de l'audio ? Non — Ray 3.2 image-to-video renvoie un MP4 muet. Ajoutez le son en post-traitement, ou utilisez un modèle audio séparé.
Combien coûte un clip ?
Facturé au clip, et comme la durée est fixe, resolution est le seul levier — un clip 1080p coûte plusieurs fois le prix d'un 540p. Les prix changent, donc consultez la page de tarifs au lieu de coder les chiffres en dur, et prototypez avec 540p.
Combien de temps attendre avant d'abandonner ?
Des dizaines de secondes à quelques minutes sont normales. Tolérez les data.status non terminaux dans la boucle d'interrogation et utilisez des délais généreux (10–15 minutes), ou enregistrez un callback avec "when": "final" pour que hiapi vous notifie au lieu de deviner les intervalles.
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