HiAPI
Blog

Tutorial

So verwendest du omnihuman-1.5 über die hiapi API: curl, Python und direkt ausführbare Anfragen

Oct 14, 2026
So verwendest du omnihuman-1.5 über die hiapi API: curl, Python und direkt ausführbare Anfragen

omnihuman-1.5 kombiniert ein Porträtfoto mit einer Audiospur und erzeugt daraus ein lippensynchrones Sprechvideo. Auf hiapi läuft es wie alle anderen Medienmodelle über dasselbe asynchrone Task-Protokoll: einmal POST /v1/tasks aufrufen, um eine Aufgabe zu erstellen, dann pollen (oder einen Webhook empfangen), bis die Video-URL bereit ist. Diese Anleitung verwendet das echte Request-Schema – Feld für Feld gegen die Live-API geprüft –, sodass der folgende curl- und Python-Code direkt kopiert und ausgeführt werden kann und nicht nur ein Beispiel ist.

Was du baust

Ein Skript, das ein Gesichtsbild und eine Sprachspur an omnihuman-1.5 sendet und eine MP4-Datei zurückgibt, in der das Gesicht passend zum Audio spricht, mit synchronen Lippen. Die Videolänge folgt deinem Audio – es gibt kein duration-Feld zum Festlegen.

Voraussetzungen

  1. Ein hiapi API-Key. Erstelle einen im Dashboard; er wird bei jeder Anfrage als Bearer-Token gesendet. Das Format des Headers findest du in der Authentifizierungsdokumentation.
  2. Eine öffentlich erreichbare Bild-URL und Audio-URL. omnihuman-1.5 lädt beide serverseitig herunter, daher genügt eine http(s)-URL, die dein Rechner anonym öffnen kann; localhost-Pfade und signierte URLs, die mitten in der Aufgabe ablaufen, funktionieren hingegen nicht.

Das war's – kein SDK, kein Konto außer dem Key erforderlich.

Eingabe-Schema

omnihuman-1.5 akzeptiert ein striktes input-Objekt. Unbekannte Felder werden abgelehnt, daher sind dies alle verfügbaren Felder:

Feld Typ Erforderlich Beschreibung
image_url string Ja Eine Porträt-URL. Ein klares, frontales einzelnes Gesicht funktioniert am besten.
audio_url string Ja Die Sprachspur für die Lippensynchronisation. Bestimmt die Ausgabelänge.
prompt string Nein Optionale Steuerung von Bewegung/Ausdruck.
resolution string Nein "720p" oder "1080p".

Es gibt keinen reinen Textmodus: audio_url ist erforderlich. Wenn du nur ein Skript hast, erzeuge zuerst die Sprache (mit einem TTS-Modell oder einem textgesteuerten Avatar wie heygen-avatar-v) und übergib dann die erzeugte Audio-URL hier.

Minimales lauffähiges Beispiel (curl)

Aufgabe erstellen:

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"
    }
  }'

Die Antwort enthält eine Task-ID:

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

Frage die Aufgabe ab, bis sie einen Endzustand erreicht:

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

Nach Abschluss ist data.status gleich "success" und das Video liegt unter data.output[0].url:

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

Die output-URL ist temporär (mit expireAt). Lade die MP4-Datei sofort nach erfolgreicher Aufgabe herunter – verlinke diese Adresse in der Produktion nicht direkt.

Derselbe Aufruf in 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")

In die Produktion bringen

Bevorzuge Callbacks gegenüber dichtem Polling. Füge ein Objekt callback auf oberster Ebene hinzu, und hiapi sendet die abgeschlossene Aufgabe per POST an deinen Endpunkt, statt dich pollen zu lassen:

{
  "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 akzeptiert nur "final" und wird sowohl bei Erfolg als auch bei Fehlschlag ausgelöst – dein Webhook muss daher anhand von data.status verzweigen. Der zugestellte Body ist dasselbe Objekt, das du über GET /v1/tasks/{id} erhältst. Die Zustellung erfolgt mit At-least-once-Semantik, daher solltest du anhand von taskId deduplizieren.

Reiche bei temporären Fehlern niemals erneut ein. Wenn eine Polling-Anfrage in ein Timeout läuft oder die Verbindung abbricht, frage dieselbe taskId erneut ab – rufe POST /v1/tasks nicht noch einmal auf. Eine erneute Einreichung startet eine zweite kostenpflichtige Aufgabe. Speichere die taskId sofort, sobald du sie hast.

Behandle die drei Fehlerformen getrennt:

  • Authentifizierungsfehler → HTTP 401, Body {"error": {"code": "permission_denied", "type": "hiapi_error", ...}}. Korrigiere den Key; nicht erneut versuchen.
  • Fehlerhafte Anfrage (fehlendes/ungültiges Feld) → HTTP 400, Body {"code": 400, "error_code": "INVALID_REQUEST", "message": "..."}. Die message nennt das problematische Feld – korrigieren und erneut senden.
  • Gestartete, aber fehlgeschlagene Aufgabe → HTTP 200 und data.status == "fail" mit data.error. Die häufigste Ursache ist, dass der Server image_url/audio_url nicht herunterladen kann. Prüfe, ob beide öffentlich erreichbar und noch gültig sind. Verzweige immer anhand von data.status und nicht nur anhand des HTTP-Statuscodes.

Die Abrechnung erfolgt pro Ausgabesekunde und ist auf der Preisseite aufgeführt; da die Cliplänge deinem Audio folgt, variieren die Kosten mit der Audiodauer.

Verwandte Anleitungen

Häufige Fragen

Kann ich omnihuman-1.5 mit Text statt einer Audiodatei steuern? Nein. audio_url ist erforderlich, und das Modell synchronisiert die Lippen auf dieses Audio. Wenn du einen Ablauf von Skript zu Video benötigst, synthetisiere zuerst die Sprache (beliebiges TTS) oder verwende einen textgesteuerten Avatar wie heygen-avatar-v und übergib dann die Audio-URL hier.

Wie lang ist das Ausgabevideo? Es folgt deinem Audioausschnitt – es gibt keinen duration-Parameter. Eine 12-sekündige Sprachspur erzeugt ungefähr ein 12-sekündiges Video.

Welche Art von Bild funktioniert am besten? Ein einzelnes Porträt einer Person mit klarer Beleuchtung und frontaler Ausrichtung unter einer öffentlichen URL. Gruppenfotos oder stark verdeckte Gesichter führen zu schlechterer Lippensynchronisation.

720p oder 1080p? Beides ist für resolution verfügbar. Beginne beim Iterieren mit 720p; wechsle für das finale Rendering auf 1080p. Höhere Auflösungen kosten pro Sekunde mehr – siehe Preise.

Meine Aufgabe gibt sofort status: "fail" zurück. Warum? Fast immer kann der Server eine der Eingabe-URLs nicht abrufen. Stelle sicher, dass image_url und audio_url öffentlich erreichbar sind (öffne sie in einem Inkognito-Fenster) und nicht abgelaufen sind. Die Meldung in data.error hilft beim Eingrenzen.

Muss ich pollen, oder kann ich einen Webhook verwenden? Beides ist möglich. Für einfache Skripte kannst du mit GET /v1/tasks/{id} pollen; in der Produktion kannst du callback.url registrieren und when auf "final" setzen, sodass du keine Polling-Schleife aufrechterhalten musst.

Ähnliche Artikel

Alle Artikel ansehen →

Starte mit persistenten KI-Artefakten

Hol dir deinen API-Key und rufe Bild-, Video- und Audiomodelle über eine einzige task-basierte API auf.

API-Dokumentation lesen