HiAPI
Blog

Tutorial

So verwendest du die hailuo 2.3 Fast Image-to-Video API: curl, Python und ein lauffähiger Request

Jul 10, 2026
So verwendest du die hailuo 2.3 Fast Image-to-Video API: curl, Python und ein lauffähiger Request

Du hast ein statisches Bild und möchtest daraus ein kurzes Video machen. Diese Anleitung zeigt dir den Zugang zu hailuo-2.3-fast/image-to-video über die hiapi Task-API: das exakte Eingabe-Schema (geprüft gegen den Live-Validator), einen direkt kopierbaren curl-Request, ein vollständiges Python-Skript, das bis zur Fertigstellung pollt, sowie Details für den Produktivbetrieb – Callbacks, idempotente Wiederholungsversuche und die Fehlerantworten, auf die du tatsächlich stoßen wirst.

Was du vorbereiten musst

  • Einen hiapi API-Schlüssel – erhältlich in deiner Konsole. Der Schlüssel hat die Form sk-... und wird im Authorization: Bearer-Header übergeben.
  • Ein Quellbild, das unter einer öffentlich erreichbaren HTTPS-URL gehostet wird. Die API erwartet eine URL, keine hochgeladene Datei.
  • Einen beliebigen HTTP-Client. Die folgenden Beispiele verwenden curl und Pythons requests.

Die sekundengenaue Preisgestaltung des Modells findest du auf der Preisseite.

Eingabe-Schema (verifiziert)

hailuo-2.3-fast/image-to-video verwendet den einheitlichen asynchronen Task-Endpunkt: POST /v1/tasks legt einen Task an, GET /v1/tasks/<taskId> fragt den Task ab. Das Eingabe-Schema ist streng – unbekannte Felder werden mit 400 abgelehnt, statt stillschweigend ignoriert zu werden.

Feld Typ Erforderlich Beschreibung
prompt string ✅ Beschreibt die gewünschte Bewegung
image_url string ✅ Singularform – ein URL-String, kein image_urls-Array
duration string optional "6" oder "10" – eine Zeichenkette, keine Zahl
prompt_optimizer boolean optional Lässt das Backend deinen Prompt für die Bewegung umschreiben

Der Validator fängt drei typische Stolperfallen ab:

  1. duration muss ein String sein. Wenn du "duration": 6 sendest, erhältst du 400 mit dem Hinweis duration: got number, want string. Sende stattdessen "duration": "6".
  2. Das Feld heißt in der Singularform image_url. Viele andere Videomodelle auf der Plattform (Grok Imagine, Kling) erwarten ein image_urls-Array. Dieses Modell erwartet einen einzelnen String.
  3. Es gibt kein resolution, aspect_ratio, seed, watermark oder end_image_url. Sie alle liefern additional properties ... not allowed. Das Ausgabeformat folgt deinem Quellbild; die Bewegung lässt sich nur über den Prompt steuern.

Minimaler lauffähiger Request: curl

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "hailuo-2.3-fast/image-to-video",
    "input": {
      "prompt": "Die Kamera fährt langsam vorwärts, Dampf steigt von der Kaffeetasse auf, sanftes Morgenlicht",
      "image_url": "https://your-cdn.example.com/coffee.jpg",
      "duration": "6"
    }
  }'

Erfolgreiche Antwort:

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

Anschließend pollen:

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

Während das Video gerendert wird, ist data.status gleich "handling":

{
  "code": 200,
  "data": {
    "taskId": "tk-hiapi-01KX4W3SVW8S8EHD5VX45JH95V",
    "model": "hailuo-2.3-fast/image-to-video",
    "status": "handling",
    "storage": "temp",
    "completed": 0,
    "created": 1783648872
  },
  "message": "success"
}

Die Endzustände sind "success" (Video-URL in data.output[0].url) und "fail" (Details in data.error).

Vollständiges Python-Skript

import os
import time

import requests

API_BASE = "https://api.hiapi.ai/v1/tasks"
TOKEN = os.environ["HIAPI_API_KEY"]  # sk-...
HEADERS = {"Authorization": f"Bearer {TOKEN}"}


def create_task(prompt: str, image_url: str, duration: str = "6") -> str:
    resp = requests.post(
        API_BASE,
        headers={**HEADERS, "Content-Type": "application/json"},
        json={
            "model": "hailuo-2.3-fast/image-to-video",
            "input": {
                "prompt": prompt,
                "image_url": image_url,
                "duration": duration,  # String: "6" oder "10"
            },
        },
        timeout=60,
    )
    body = resp.json()
    if resp.status_code != 200 or not body.get("data", {}).get("taskId"):
        raise RuntimeError(f"create failed: {resp.status_code} {body}")
    return body["data"]["taskId"]


def wait_task(task_id: str, timeout_s: int = 600, poll_s: int = 5) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        task = requests.get(f"{API_BASE}/{task_id}", headers=HEADERS, timeout=30).json()["data"]
        status = task.get("status")
        if status == "success":
            return task
        if status == "fail":
            err = task.get("error") or {}
            raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
        time.sleep(poll_s)
    raise TimeoutError(f"task {task_id} still running after {timeout_s}s")


if __name__ == "__main__":
    task_id = create_task(
        prompt="Die Katze blinzelt, dreht den Kopf zum Fenster, eine Brise streicht über ihr Fell",
        image_url="https://your-cdn.example.com/cat.jpg",
        duration="6",
    )
    print("task:", task_id)

    task = wait_task(task_id)
    video_url = task["output"][0]["url"]
    print("video:", video_url)

    # Die URL liegt im temporären Speicher mit Ablaufzeit – lade sie sofort herunter und verlinke sie nicht direkt.
    mp4 = requests.get(video_url, timeout=120).content
    with open("output.mp4", "wb") as f:
        f.write(mp4)
    print(f"saved output.mp4 ({len(mp4)} bytes)")

Dieses Skript macht zwei Dinge richtig, die schnell zusammengeschusterte Lösungen oft übersehen:

  • Es lädt das Video sofort herunter. storage: "temp" bedeutet, dass die Ausgabe-URL eine Ablaufzeit (expireAt) hat. Persistiere die Bytes in deinem eigenen Speicher; speichere die zurückgegebene URL niemals in deiner Datenbank.
  • Es behandelt fail als Endzustand. Ein erneutes Pollen eines fehlgeschlagenen Tasks belebt ihn nicht wieder – lege einen neuen Task an.

Hinweise für den Produktivbetrieb

Callbacks statt Polling verwenden

Für Anwendungsfälle außerhalb von Skripten registriere beim Anlegen des Tasks einen Webhook und überspringe die Polling-Schleife komplett:

{
  "model": "hailuo-2.3-fast/image-to-video",
  "input": { "prompt": "...", "image_url": "https://...", "duration": "6" },
  "callback": { "url": "https://your-app.example.com/hooks/hiapi", "when": "final" }
}

Mit "when": "final" wird dein Endpunkt einmal aufgerufen, wenn der Task success oder fail erreicht. Faustregel: Polling eignet sich für CLIs, Batch-Jobs und alle temporären Szenarien; sobald du einen Server hast, der bereits lauscht, ist ein Callback überlegen – Videotasks laufen mehrere Minuten, und eine Polling-Schleife ist in diesen Minuten ein belegter Verbindungsslot und ein verschwendeter Worker.

Idempotenz

Der Create-Aufruf selbst ist nicht idempotent – jedes POST /v1/tasks, das die Validierung besteht, legt einen neuen (kostenpflichtigen) Task an. Wenn dein Task-Runner bei einem Timeout erneut versucht, speichere die erhaltene taskId vor der Verarbeitung des Ergebnisses, verschlüsselt mit deiner eigenen Request-ID. Wenn bei einem Wiederholungsversuch für diesen Schlüssel bereits eine taskId existiert, pollst du sie, statt einen doppelten Task anzulegen.

Fehler, auf die du tatsächlich stoßen wirst

Symptom Bedeutung Behebung
401 — {"error":{"code":"permission_denied","type":"hiapi_error"}} Der Schlüssel ist ungültig, oder er darf dieses Modell nicht verwenden Prüfe den Schlüssel in der Konsole; die Antwort enthält eine request_id für den Support
400 INVALID_REQUEST — duration: got number, want string Es wurde eine JSON-Zahl statt eines Strings verwendet "duration": "6"
400 INVALID_REQUEST — additional properties '...' not allowed Du hast ein Feld außerhalb des Schemas gesendet Entferne es – die Meldung benennt jedes fehlerhafte Feld
400 INVALID_REQUEST — missing required field "prompt" / "image_url" Die Eingabe ist leer oder der Feldname ist falsch Beide Felder sind erforderlich
Der Task erreicht nach dem Anlegen fail Laufzeitfehler – häufige Ursache ist eine Bild-URL, die das Backend nicht abrufen kann Der Validator ruft dein Bild beim Anlegen nicht ab; stelle sicher, dass die URL öffentlich erreichbar ist

Die letzte Zeile verdient besondere Betonung: Die Validierung beim Anlegen betrifft nur das Schema. Eine falsch geschriebene oder private image_url liefert dir eine scheinbar normale taskId und einen Task, der nach einigen Minuten stirbt – alarmiere daher bei fail, nicht nur bei HTTP-Fehlern.

Weiterführende Lektüre

Häufig gestellte Fragen

Kann ich Auflösung oder Seitenverhältnis festlegen? Nein. Das Schema von hailuo-2.3-fast/image-to-video lehnt resolution und aspect_ratio als unbekannte Felder ab. Das Ausgabeformat folgt deinem Quellbild; wenn du ein bestimmtes Seitenverhältnis brauchst, schneide das Eingabebild vorher zu.

Welche Dauern werden unterstützt? Genau zwei: "6" und "10" Sekunden, übergeben als String. Jeder andere Wert liefert 400 mit dem Hinweis value must be one of '6', '10'.

Kann ich mehrere Referenzbilder übergeben? Nein – das Feld ist ein einzelner image_url-String. Modelle, die Bild-Arrays akzeptieren (etwa image_urls bei Grok Imagine), verwenden ein anderes Schema; kopiere Request-Bodies nicht ungeprüft zwischen Modellen hin und her.

Wie steuere ich die Bewegung? Nur über den prompt – es gibt keine Parameter für Bewegungsintensität oder Kameraführung. Beschreibe die Bewegung explizit („Die Kamera fährt langsam vorwärts“, „Das Haar weht im Wind“). Optional kannst du "prompt_optimizer": true setzen, damit das Backend deinen Prompt erweitert.

Gibt es eine langsamere Variante mit höherer Qualität? Ja – hailuo-2.3/image-to-video (ohne -fast) ist ebenfalls auf der Plattform verfügbar, und hailuo-2.3/text-to-video deckt Szenarien ohne Quellbild ab. Prüfe vor dem Wechsel das Schema jeder Variante; die Eingabefelder sind nicht garantiert identisch.

Was kostet es? Die Abrechnung erfolgt nach Sekunden des generierten Videos und variiert je nach Modell; die aktuellen Sätze findest du auf der Preisseite.

Brauche ich zum Ausprobieren einen API-Schlüssel? Ja – alle Task-API-Aufrufe erfordern einen Bearer sk-...-Schlüssel. Du kannst einen in der Konsole anlegen und zuerst im Playground auf der Modellseite testen.

Ä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