HiAPI
Blog

Tutorial

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

Jul 11, 2026
So verwendest du die hailuo 2.3 Image-to-Video API: curl, Python und ein lauffähiges Request-Beispiel

Zusammenfassung

  • Was das ist: Ein lauffähiges Rezept zur Verwendung der hailuo-2.3/image-to-video API auf hiapi – verwandle ein statisches Bild in einen kurzen Clip: Task erstellen, Task abfragen, .mp4 herunterladen.
  • Endpunkt: POST https://api.hiapi.ai/v1/tasks mit "model": "hailuo-2.3/image-to-video" im Request-Body, dann GET /v1/tasks/<taskId> abfragen, bis der Task einen Endzustand erreicht.
  • Zwei Pflicht-Eingabefelder: prompt und image_url – eine einzelne öffentliche URL zu deinem ersten Frame (kein Array).
  • duration ist ein String, keine Zahl: "6" oder "10". Ein Integer 6 wird mit 400 abgelehnt.
  • Das Schema ist strikt. Es gibt keine Parameter motion_strength, camera_movement oder resolution – wer sie sendet, erhält 400. Bewegung wird ausschließlich über den Prompt gesteuert.

Was du baust und was du vorbereiten musst

Du hast ein statisches Bild – ein Produktfoto, ein Charakter-Rendering, ein Foto – und möchtest, dass hailuo-2.3 es zum Leben erweckt und einen Videoclip erzeugt, der genau bei diesem Frame beginnt. hailuo-2.3 ist bekannt für physikalisch glaubwürdige Bewegung (Stoff, Haare, Körpermchanik), eignet sich also hervorragend, um statische Bilder zu beleben.

Du brauchst zwei Dinge:

  1. Einen hiapi API-Key. Erstelle ihn im Dashboard. Der Key sieht aus wie sk-... und gehört in den Header Authorization: Bearer.
  2. Eine öffentlich erreichbare URL für das erste Frame. Die API lädt das Bild von image_url, daher muss es ohne Authentifizierung erreichbar sein – S3-/R2-/CDN-Links funktionieren; localhost-Pfade oder abgelaufene signierte URLs nicht.

Wie jedes generative Modell auf hiapi nutzt auch dieses die asynchrone Task-Schnittstelle: Du POSTest einen Task, erhältst sofort eine taskId und holst das Ergebnis danach per Polling oder Callback. Für Videos gibt es keinen synchronen Endpunkt – das Rendern eines Clips dauert länger als typische HTTP-Timeouts.

Request-Schema

Das vollständige Eingabeschema für hailuo-2.3/image-to-video – gegen die Live-API verifiziert:

Feld Typ Erforderlich Beschreibung
prompt string ✅ Beschreibe die Bewegung, nicht die Szene – die Szene kommt aus deinem Bild.
image_url string ✅ Eine öffentliche URL, das erste Frame. Singular – nicht image_urls.
duration string optional "6" oder "10" (Sekunden). Muss ein String sein.
prompt_optimizer boolean optional Lässt die Plattform deinen Prompt vor der Generierung erweitern.

Das Schema ist strikt: Alles, was nicht in dieser Tabelle steht, wird abgelehnt. Sendest du motion_strength, camera_movement oder resolution, erhältst du genau das zurück:

{
  "code": 400,
  "error_code": "INVALID_REQUEST",
  "message": "invalid input: <root>: additional properties 'resolution', 'camera_movement', 'motion_strength' not allowed"
}

Zwei Konsequenzen solltest du dir merken:

  • Kein Regler für Bewegungsstärke. Die Bewegungsphysik von hailuo-2.3 wird ausschließlich durch den Prompt gesteuert – siehe den Abschnitt zum Prompt-Schreiben unten.
  • Keine Kontrolle über Auflösung oder Seitenverhältnis an diesem Endpunkt. Baue keine UI für Parameter, die die API mit 400 ablehnt.

Minimales lauffähiges Beispiel: curl

Task erstellen:

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-<your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "hailuo-2.3/image-to-video",
    "input": {
      "prompt": "The woman turns toward the camera and smiles; wind gently moves her hair; slow cinematic push-in",
      "image_url": "https://your-cdn.example.com/first-frame.jpg",
      "duration": "6",
      "prompt_optimizer": true
    }
  }'

Die Antwort (Auszug) enthält die Task-ID unter data.taskId:

{ "data": { "taskId": "..." } }

Task abfragen:

curl -s https://api.hiapi.ai/v1/tasks/<taskId> \
  -H "Authorization: Bearer sk-<your-key>"

Während der Clip gerendert wird, meldet data.status einen Nicht-Endzustand. Sobald es auf "success" wechselt, steht die Video-URL in data.output[0].url; bei "fail" erklären data.error.code und data.error.message den Grund.

Lade die Datei sofort nach erfolgreichem Task herunter. Die Ausgabe-URL ist signiert und hat ein Ablaufdatum (expireAt) – speichere die Bytes in deinem eigenen Speicher und verlinke niemals direkt auf die Task-URL.

Minimales lauffähiges Beispiel: Python

Vollständiges Skript für denselben Ablauf – erstellen, abfragen, herunterladen:

import time
import requests

API_BASE = "https://api.hiapi.ai/v1/tasks"
API_KEY = "sk-<your-key>"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}


def create_task() -> str:
    payload = {
        "model": "hailuo-2.3/image-to-video",
        "input": {
            "prompt": (
                "The cat stretches, hops off the windowsill, and walks toward "
                "the camera; soft afternoon light, subtle handheld camera sway"
            ),
            "image_url": "https://your-cdn.example.com/first-frame.jpg",
            "duration": "6",          # string, not int: "6" or "10"
            "prompt_optimizer": True,
        },
    }
    r = requests.post(API_BASE, json=payload, headers=HEADERS, timeout=30)
    task_id = (r.json().get("data") or {}).get("taskId")
    if not task_id:
        raise RuntimeError(f"create failed ({r.status_code}): {r.text[:300]}")
    return task_id


def wait_task(task_id: str, timeout_s: int = 900, poll_s: int = 10) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(f"{API_BASE}/{task_id}", headers=HEADERS, timeout=30)
        task = r.json().get("data") or {}
        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} not finished after {timeout_s}s")


def main() -> None:
    task_id = create_task()
    print("task created:", task_id)
    task = wait_task(task_id)
    url = task["output"][0]["url"]
    # The URL is signed and expires -- download immediately, store the bytes yourself.
    video = requests.get(url, timeout=120).content
    with open("hailuo-output.mp4", "wb") as f:
        f.write(video)
    print("saved hailuo-output.mp4")


if __name__ == "__main__":
    main()

Video-Tasks laufen deutlich länger als Bild-Tasks, daher fragt das Skript alle 10 Sekunden ab und setzt ein großzügiges Limit von 15 Minuten. Frage nicht aggressiver als etwa alle 5 Sekunden ab – das macht das Rendern nicht schneller.

Prompts schreiben, die Bewegung steuern

Da es keine Bewegungsparameter gibt, ist der Prompt dein einziges Steuerrad – und für hailuo-2.3 reicht das aus. Drei Faustregeln:

  • Beschreibe Bewegung, nicht Aussehen. Das Modell hat dein Bild bereits gesehen. "a beautiful woman in a red dress" verschwendet den Prompt; "she turns her head slowly to the left as the camera pushes in" nutzt ihn gut.
  • Beschreibe Kamerabewegung in einfacher Sprache. "slow push-in", "orbit right", "handheld sway", "static camera" – solche Angaben gehören in den Prompt-Text, nicht in ein (nicht existierendes) Feld camera_movement.
  • Lass bei Unsicherheit prompt_optimizer helfen. Mit "prompt_optimizer": true erweitert die Plattform kurze Prompts vor der Generierung – ein guter Standard für kurze Eingaben. Wenn du eine sorgfältig formulierte Beschreibung strikt befolgen willst, schalte ihn ab.

Hinweise für den Produktivbetrieb

Bevorzuge Callbacks statt Polling. Gib beim Erstellen des Tasks einen Callback mit, und hiapi POSTet das Endergebnis an deinen Endpunkt, sodass du keine offene Schleife aufrechterhalten musst:

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

"when": "final" wird nur einmal im Endzustand ausgelöst. Polling eignet sich für Skripte und Notebooks; Callbacks sind die richtige Form für Server – das Rendern von Videos dauert lang genug, dass ein Worker, der beim Polling blockiert, echte Kosten verursacht.

Behandle diese Fehler explizit:

  • 401 mit "code": "permission_denied" – der Key ist fehlerhaft, oder der Key darf dieses Modell nicht verwenden. Die Meldung enthält eine request_id, die du an den Support senden kannst.
  • 400 INVALID_REQUEST – Schema-Verstoß. Die Meldung ist präzise, z. B. duration: got number, want string oder image_url: missing required field "image_url". Protokolliere sie unverändert.
  • 402 beim Erstellen des Tasks – unzureichendes Guthaben. Beachte, dass Videos pro Task teurer sind als Bilder, sodass ein Key, mit dem Bild-Tasks noch funktionieren, bei Video-Tasks bereits fehlschlagen kann. Lade Guthaben auf oder prüfe den Verbrauch im Dashboard.
  • Der Task erreicht "fail" – Fehler auf der Generierungsseite; prüfe data.error. Ein häufiger Grund speziell bei Image-to-Video: image_url kann nicht geladen werden (privater Bucket, abgelaufene signierte URL).

Mache den Ablauf rund um Wiederholungen idempotent. Wenn dein Prozess zwischen Erstellen und Herunterladen abstürzt, erzeugt ein naiver Neustart ein zweites (kostenpflichtiges) Rendering. Persistiere die taskId sofort nach der Rückkehr von create_task und fahre beim Neustart mit dem Polling fort, statt neu zu erstellen.

Weiterführende Lektüre

Häufige Fragen

Welche Dauern unterstützt hailuo-2.3 Image-to-Video? Zwei: "6" und "10" Sekunden, übergeben als String. Jeder andere Wert – oder derselbe Wert als Zahl – führt zu 400 INVALID_REQUEST.

Warum schlägt "duration": 6 fehl, während "duration": "6" funktioniert? Das Schema definiert duration als String-Enum. Die API sagt es dir eindeutig: duration: got number, want string. Das ist der häufigste 400 an diesem Endpunkt.

Kann ich die Ausgabeauflösung oder das Seitenverhältnis festlegen? Nein. Der Endpunkt stellt keine Felder resolution oder aspect_ratio bereit; eines davon zu senden ergibt 400 – das Schema lehnt unbekannte Eigenschaften ab, statt sie zu ignorieren.

Wie steuere ich Kamerabewegung oder Bewegungsstärke? Über den Prompt. Es gibt keine Parameter camera_movement oder motion_strength – schreibe Kamera- und Bewegungsanweisungen in einfacher Sprache ("slow push-in", "she walks toward the camera").

Kann ich mehrere Referenzbilder übergeben? Nein – image_url ist ein einzelner String, das erste Frame. Das unterscheidet sich von einigen anderen Modellen auf hiapi, die ein Array image_urls entgegennehmen; das Schema wird pro Modell definiert, sieh dir also unbedingt die Modellseite an.

Welche Bildformate werden für das erste Frame unterstützt? Standard-Webformate (JPEG/PNG), bereitgestellt über eine öffentlich erreichbare URL. Wenn die Plattform die URL nicht laden kann, schlägt der Task nach der Erstellung fehl – prüfe daher vor dem Absenden, dass der Link öffentlich erreichbar ist.

Worin unterscheidet sich das von hailuo-2.3/text-to-video? Text-to-Video erfindet die Szene allein aus deinem Prompt; Image-to-Video fixiert das erste Frame an dein Bild und beginnt die Animation von dort. Der Task-Lebenszyklus ist identisch – nur das Eingabeschema unterscheidet sich (hier ist image_url erforderlich, während Text-to-Video überhaupt kein Bildfeld hat).

Was kostet das? Videos werden nach Ausgabesekunden abgerechnet, und die Tarife ändern sich – sieh auf der hailuo-2.3/image-to-video Modellseite und der Preisseite nach den aktuellen Werten.

Ä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