HiAPI
Blog

Tutoriel

Comment utiliser omnihuman-1.5 via l'API hiapi : curl, Python et requêtes directement exécutables

Oct 14, 2026
Comment utiliser omnihuman-1.5 via l'API hiapi : curl, Python et requêtes directement exécutables

omnihuman-1.5 transforme une photo de portrait et une piste audio en une vidéo parlante à synchronisation labiale. Sur hiapi, comme tous les autres modèles média, il fonctionne sur le même protocole de tâche asynchrone : appelez d'abord POST /v1/tasks pour créer une tâche, puis interrogez (ou recevez un webhook) jusqu'à ce que l'URL de la vidéo soit prête. Ce guide utilise le schéma de requête réel — vérifié champ par champ avec l'API en production — afin que le code curl et Python ci-dessous puisse être copié et exécuté tel quel, et non comme simple exemple.

Ce que vous allez construire

Un script qui envoie une image de visage et une piste vocale à omnihuman-1.5 et renvoie un fichier MP4 où ce visage parle en synchronisation labiale avec l'audio. La durée du clip suit votre audio — il n'y a pas de champ duration à définir.

Prérequis

  1. Une clé API hiapi. Créez-en une dans le dashboard ; elle est envoyée comme Bearer token à chaque requête. Consultez la documentation d'authentification pour le format de l'en-tête.
  2. Une URL d'image et une URL audio accessibles publiquement. omnihuman-1.5 les télécharge côté serveur, donc une URL http(s) que votre machine peut ouvrir anonymement suffit ; les chemins localhost et les URL signées qui expirent en cours de tâche ne fonctionnent pas.

C'est tout — pas de SDK, aucun compte nécessaire en dehors de la clé.

Schéma d'entrée

omnihuman-1.5 accepte un objet input strict. Les champs inconnus sont rejetés, voici donc l'ensemble des champs disponibles :

Champ Type Obligatoire Description
image_url string Oui URL d'un portrait. Un visage unique, net et de face donne les meilleurs résultats.
audio_url string Oui Piste vocale utilisée pour la synchronisation labiale. Détermine la durée de sortie.
prompt string Non Guidage optionnel pour l'animation/les expressions.
resolution string Non "720p" ou "1080p".

Il n'y a pas de mode texte seul : audio_url est obligatoire. Si vous n'avez qu'un script, générez d'abord la voix (avec un modèle TTS, ou un avatar numérique piloté par texte comme heygen-avatar-v), puis passez l'URL audio générée ici.

Exemple minimal exécutable (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": "omnihuman-1.5",
    "input": {
      "image_url": "https://example.com/portrait.jpg",
      "audio_url": "https://example.com/voiceover.mp3",
      "resolution": "720p"
    }
  }'

La réponse contient l'id de tâche :

{ "code": 200, "data": { "taskId": "b3f1c2e8-...", "status": "pending" } }

Interrogez la tâche jusqu'à ce qu'elle atteigne un état terminal :

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

Une fois terminée, data.status vaut "success" et la vidéo se trouve dans data.output[0].url :

{
  "code": 200,
  "data": {
    "taskId": "b3f1c2e8-...",
    "status": "success",
    "output": [
      { "url": "https://temp.hiapi.ai/.../omnihuman.mp4", "expireAt": 1760000000 }
    ]
  }
}

Cette URL output est temporaire (elle porte un expireAt). Téléchargez le MP4 dès que la tâche réussit — ne faites pas de hotlink vers cette adresse en production.

Le même appel en Python

import time
import requests  # pip install requests

API = "https://api.hiapi.ai/v1/tasks"
KEY = "sk-YOUR_KEY"
HEADERS = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}


def create_task() -> str:
    payload = {
        "model": "omnihuman-1.5",
        "input": {
            "image_url": "https://example.com/portrait.jpg",
            "audio_url": "https://example.com/voiceover.mp3",
            "resolution": "720p",
        },
    }
    r = requests.post(API, headers=HEADERS, json=payload, timeout=60)
    r.raise_for_status()
    return r.json()["data"]["taskId"]


def wait_for_video(task_id: str, timeout: int = 600, interval: int = 5) -> str:
    deadline = time.time() + timeout
    while time.time() < deadline:
        r = requests.get(f"{API}/{task_id}", headers=HEADERS, timeout=30)
        data = r.json()["data"]
        status = data["status"]
        if status == "success":
            return data["output"][0]["url"]
        if status == "fail":
            raise RuntimeError(f"omnihuman-1.5 task failed: {data.get('error')}")
        time.sleep(interval)
    raise TimeoutError(f"task {task_id} did not finish in {timeout}s")


if __name__ == "__main__":
    tid = create_task()
    print("task:", tid)
    video_url = wait_for_video(tid)
    # URLs expire — persist the bytes immediately.
    mp4 = requests.get(video_url, timeout=120).content
    with open("talking_head.mp4", "wb") as f:
        f.write(mp4)
    print("saved talking_head.mp4")

Passer en production

Privilégiez les callbacks plutôt qu'un polling intensif. Ajoutez un objet callback au niveau racine et hiapi enverra en POST la tâche terminée à votre endpoint, au lieu de vous laisser interroger :

{
  "model": "omnihuman-1.5",
  "input": {
    "image_url": "https://example.com/portrait.jpg",
    "audio_url": "https://example.com/voiceover.mp3",
    "resolution": "1080p"
  },
  "callback": { "url": "https://your.app/webhooks/hiapi", "when": "final" }
}

when n'accepte que "final", et se déclenche en cas de succès et d'échec — votre webhook doit donc brancher selon data.status. Le corps livré est le même objet que celui obtenu via GET /v1/tasks/{id}. La livraison est de type au moins une fois, dédupliquez donc par taskId.

Ne resoumettez jamais sur une erreur temporaire. Si une requête de polling expire ou si la connexion est interrompue, interrogez à nouveau le même taskId — n'appelez pas POST /v1/tasks une seconde fois. Une resoumission démarre une deuxième tâche facturée. Enregistrez le taskId dès que vous l'obtenez.

Traitez séparément les trois formes d'échec :

  • Échec d'authentification → HTTP 401, body {"error": {"code": "permission_denied", "type": "hiapi_error", ...}}. Corrigez la clé ; ne réessayez pas.
  • Requête invalide (champ manquant/invalide) → HTTP 400, body {"code": 400, "error_code": "INVALID_REQUEST", "message": "..."}. Le message indique le champ problématique — corrigez puis renvoyez.
  • Tâche démarrée mais échouée → HTTP 200, avec data.status == "fail" et data.error. La cause la plus fréquente est que le serveur ne peut pas télécharger image_url/audio_url. Vérifiez que les deux sont accessibles publiquement et toujours valides. Branchez toujours sur data.status, pas seulement sur le code de statut HTTP.

Le prix est facturé à la seconde de sortie et indiqué sur la page de tarifs ; comme la durée du clip suit votre audio, le coût varie selon la durée de l'audio.

Guides associés

FAQ

Puis-je piloter omnihuman-1.5 avec du texte plutôt qu'un fichier audio ? Non. audio_url est obligatoire et le modèle synchronise les lèvres sur cet audio. Pour un flux script-vers-vidéo, synthétisez d'abord la voix (n'importe quel TTS), ou utilisez un avatar numérique piloté par texte comme heygen-avatar-v, puis passez l'URL audio ici.

Quelle est la durée de la vidéo de sortie ? Elle suit votre piste audio — il n'y a pas de paramètre duration. Une piste vocale de 12 secondes produit environ 12 secondes de vidéo.

Quelle image donne les meilleurs résultats ? Un portrait unique, de face, avec un éclairage net, hébergé sur une URL publique. Les photos de groupe ou les visages fortement masqués donnent une synchronisation labiale moins bonne.

720p ou 1080p ? Les deux sont acceptés dans resolution. Commencez par 720p pendant les itérations ; passez à 1080p pour le rendu final. Une résolution plus élevée coûte plus cher par seconde — voir pricing.

Ma tâche renvoie immédiatement status: "fail". Pourquoi ? Presque toujours parce que le serveur ne parvient pas à récupérer une URL d'entrée. Vérifiez que image_url et audio_url sont accessibles publiquement (ouvrez-les dans une fenêtre de navigation privée) et non expirées. Le message data.error aide à cerner le problème.

Dois-je faire du polling, ou puis-je utiliser un webhook ? Les deux sont possibles. Un script simple peut interroger via GET /v1/tasks/{id} ; en production, enregistrez callback.url avec when défini à "final" pour éviter de maintenir une boucle de polling.

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