Tutorial
So verwendest du die Ray 3.2 Image-to-Video API: curl, Python und eine Anfrage, die auf Anhieb funktioniert
Ray 3.2 Image-to-Video animiert ein einzelnes Standbild: Du gibst ihm ein Bild und einen Prompt, der beschreibt, wie es sich bewegen soll, und es liefert einen kurzen Clip zurück, der mit deinem Bild beginnt. Auf hiapi lautet die Modell-ID ray-3.2/image-to-video, und du rufst sie über dieselbe einheitliche asynchrone Task-API wie alle anderen Videomodelle auf – POST /v1/tasks, gefolgt von Polling oder einem Callback.
Es ist ein anderer Endpoint als Ray 3.2 Text-to-Video, nicht nur ein anderer Prompt. Sein Eingabe-Schema ist kleiner und strenger, und zwei Steuerungsparameter, die du vom Textmodell gewohnt sein könntest, fehlen. Diese Anleitung liefert dir eine Anfrage, die beim ersten Versuch durchläuft: das exakte Schema (am 2026-10-20 gegen die Live-API verifiziert), curl zum direkten Kopieren, ein vollständiges Python-Skript zum Einreichen/Pollen/Herunterladen sowie Produktionsdetails – Callbacks, Idempotenz und echte Fehlerantworten, einschließlich der Fehlerart, die nicht dort auftritt, wo du sie erwartest.
Was du baust und was du vorbereiten musst
Ziel: Reiche ein Erstbild plus einen Bewegungsprompt ein, polle den Task und lade dann ein stummes .mp4 herunter, das dein Bild in Bewegung versetzt.
Voraussetzungen:
- Ein hiapi-API-Key – hole dir einen aus deinem Dashboard. Der Key sieht aus wie
sk-...und wird imAuthorization: Bearer-Header übergeben. Neu bei der Task-API? Lies zuerst den Quickstart. - Eine öffentlich erreichbare Bild-URL für das Erstbild (Details siehe unten). Einen beliebigen HTTP-Client – wir zeigen
curlund Python (requests).
Ray 3.2 Image-to-Video wird pro Videoclip abgerechnet. Da die Cliplänge fest ist (siehe unten), ist resolution der einzige Kostenhebel, den du kontrollieren kannst – ein 1080p-Clip kostet ein Vielfaches eines 540p-Clips. Die aktuellen Preise findest du unter Pricing; prototypisiere beim Iterieren am Prompt zunächst mit 540p.
Das exakte Eingabe-Schema (zwei Pflichtfelder, ein optionales)
Alles liegt innerhalb des input-Objekts. Das Schema ist strikt – es weist jedes nicht erkannte Feld zurück. Feld für Feld gegen die Live-API verifiziert:
| Feld | Typ | Erforderlich | Erlaubte Werte | Standard |
|---|---|---|---|---|
prompt |
string | Ja | Wie sich das Bild bewegen soll (Kamera, Bewegung des Motivs) | — |
image_url |
string | Ja | Öffentliche URL des Erstbilds | — |
resolution |
string | Nein | 540p, 720p, 1080p |
540p |
Drei Stolpersteine, in die Leute geraten, die vom Text-to-Video-Modell kommen:
- Kein
duration. Die Cliplänge ist fest (ca. 5 Sekunden). Ein mitgeschicktesdurationwird nicht abgeschnitten – es wird direkt abgelehnt. Das ist genau umgekehrt zu Ray 3.2 Text-to-Video, wodurationeine wählbare ganze Zahl ist (5 oder 10). - Kein
aspect_ratio. Das Ausgabeformat folgt deinem Eingabebild, es gibt also nichts einzustellen. Beim Textmodell wählst du das Seitenverhältnis; hier bestimmt es das Bild. (Bestätigt unten durch einen echten Lauf: quadratische Eingabe erzeugte einen quadratischen Clip.) - Das Schema ist geschlossen.
seed,negative_prompt,prompt_optimizerund jedes andere Feld liefern einen 400 mitadditional properties '<field>' not allowed. Übernimm keine Request-Bodies von anderen Modellen in der Annahme, überzählige Schlüssel würden ignoriert.
Was die Validierung tatsächlich zurückgibt
Dies sind echte Antworten vom Live-Endpoint – absichtlich mit ungültigen Werten übermittelt, es wurde kein Task erstellt und nichts abgerechnet:
// 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"}
Die message benennt das konkrete Problemfeld, die API selbst ist also deine Schema-Referenz, wenn du etwas vergisst.
Das Erstbild vorbereiten
image_url muss eine URL sein, die der hiapi-Worker abrufen kann – ein öffentlicher Link, kein lokaler Pfad und kein data:-Base64-Blob. Das Bild bestimmt das Seitenverhältnis und die Anfangskomposition des Clips, beginne also mit genau dem Frame, an dem die Bewegung einsetzen soll.
Eine Falle, die es wert ist, vorab zu erwähnen, weil sie leicht übersehen wird: Eine fehlerhafte image_url wird beim Anlegen des Tasks nicht erkannt. Der Create-Aufruf validiert nur die Form der Anfrage (ist es ein String?), nicht ob die URL auflösbar ist. Eine nicht erreichbare oder 404-Bild-URL liefert ein völlig normales 200 mit taskId – und dann kippt der Task nach einigen Sekunden auf fail. Die exakte Fehler-Payload sehen wir im Abschnitt zur Fehlerbehebung; die Kernaussage ist: Verzweige immer nach dem terminalen status des Tasks und gehe nie davon aus, dass ein 200 beim Anlegen Erfolg bedeutet.
Minimales funktionierendes Beispiel: curl
Task anlegen:
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"
}
}'
Die Create-Antwort enthält eine Task-ID:
{ "code": 200, "data": { "taskId": "tk-hiapi-01M5..." }, "message": "success" }
Poll, bis der Task einen terminalen Zustand erreicht:
curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-01M5... \
-H "Authorization: Bearer sk-YOUR_KEY"
Während der Task läuft, durchläuft data.status mehrere laufende Zustände (du wirst Werte wie handling sehen, dann archiving). Die beiden terminalen Zustände sind success und fail. Bei Erfolg:
{
"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"
}
Lade data.output[0].url sofort herunter. Es ist eine signierte URL auf temp.hiapi.ai mit expireAt (Unix-Zeitstempel, etwa eine Woche später) – speichere die Bytes in deinem eigenen Speicher, statt sie hot zu verlinken. Ray 3.2 liefert stumme MP4s zurück; es gibt keine Tonspur, auf die du warten müsstest.
Vollständiges Python-Skript (Einreichen → Pollen → Herunterladen)
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)")
Führe es aus mit pip install requests, ersetze deinen Key und die echte image_url, und wenn der Task fertig ist, liegt eine .mp4 auf deiner Festplatte.
Wie ein echter Lauf aussieht
Um die Zahlen konkret zu machen, hier ein echter 540p-Lauf gegen die Live-API am 2026-10-20. Erstbild: ein 1600×1600 (quadratisches) Bild; Prompt: langsamer filmischer Push-in.
- Der Task durchlief
handling → archiving → success, die serverseitige Arbeit war in etwa 45 Sekunden erledigt (createderstellt,completedabgeschlossen) – mit der 8-Sekunden-Poll-Schleife dauerte es in der Praxis etwa 60 Sekunden. Video-Tasks brauchen Dutzende Sekunden bis Minuten, nicht den Bruchteil einer Sekunde; plane großzügige Deadlines ein. - Die Ausgabe war eine 720×720 MP4, ca. 1,3 MB, 5,04 Sekunden, stumm.
Dieser Lauf zeigt dir zwei Dinge, die die Parametertabelle nicht erwähnt:
- Das Ausgabe-Seitenverhältnis entspricht dem Eingabe-Seitenverhältnis – quadratisch rein, quadratisch raus. Das ist der Grund, warum es kein
aspect_ratio-Feld gibt; frame deineimage_urlso, wie du den Clip komponiert haben willst. resolutionist eine Qualitätsstufe, keine wörtliche Pixelhöhe. Bei einer 1600×1600-Eingabe und angefordertem540pkam 720×720 zurück – die Stufe skaliert anhand der Eingabeform, statt eine feste Größe zu erzwingen. Wenn du exakte Ausgabemaße brauchst, skaliere den gelieferten Clip selbst.
Produktionshinweise: Callbacks, Idempotenz, Fehler
Bevorzuge Callbacks gegenüber Polling
Für alles jenseits von Skripten registriere beim Anlegen einen Webhook statt zu pollen:
{
"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" }
}
Mit "when": "final" ruft hiapi deinen Endpoint einmal auf, wenn der Task einen terminalen Zustand erreicht – kein Polling-Traffic und keine zwischen den Intervallen verpassten Abschlüsse. Dein Handler führt dann GET /v1/tasks/<id> aus, um output[0].url zu holen. Für CLIs und Notebooks, die keinen HTTPS-Endpoint bereitstellen können, ist Polling weiterhin in Ordnung.
Mach Wiederholungen idempotent
Persistiere die taskId in dem Moment, in dem der Create-Aufruf zurückkehrt. Wenn dein Prozess mitten in der Generierung abstürzt, setze durch Pollen der gespeicherten ID fort – lege den Task nicht neu an, sonst zahlst du für einen zweiten Clip. Lege ihn nur dann neu an, wenn die ursprüngliche Create-Anfrage fehlgeschlagen ist, bevor sie eine Task-ID zurückgegeben hat. Die oben gezeigte ausfallsichere Poll-Schleife befolgt diese Regel bereits: Bei einer abgebrochenen Netzwerkanfrage wird dieselbe ID erneut gepollt, statt von vorn zu beginnen.
Fehler, die du tatsächlich sehen wirst
Jede Zeile in der folgenden Tabelle ist eine verifizierte Live-Antwortform:
| Symptom | Antwort | Behebung |
|---|---|---|
| Fehlender/ungültiger Key | 401, verschachtelt {"error":{"code":"permission_denied","type":"hiapi_error","request_id":"...","message":"This API key is invalid..."}} |
Prüfe den Authorization: Bearer sk-...-Header und ob der Key Zugriffsrechte hat. Gib beim Kontakt mit dem Support die request_id an. |
Fehlende image_url |
400 INVALID_REQUEST, message: "image_url: missing required field" |
Dieses Modell erfordert image_url – es gibt keinen reinen Textmodus. |
| Falsche Enum / überzähliges Feld | 400 INVALID_REQUEST, flach {"code":400,"error_code":"INVALID_REQUEST","message":"..."} |
Lies das in der message genannte Feld. Bei duration/aspect_ratio steht dort additional properties ... not allowed – entferne sie. |
| Falsche Task-ID beim Pollen | 404, flach {"code":404,"data":null,"message":"task not found"} (Hinweis: bei 404 gibt es kein error_code) |
Falsche ID, oder du pollst mit dem Key eines anderen Kontos. |
image_url nicht erreichbar |
Create liefert 200 + eine taskId; der Task endet anschließend mit data.status:"fail" und data.error:{"code":"INVALID_REQUEST","message":"Invalid request parameters for this model..."} |
Das ist die Falle. Der Create-Aufruf kann einen toten Link nicht von einem lebenden unterscheiden. Stelle sicher, dass dein Bild öffentlich erreichbar ist, und lies immer den terminalen data.status. |
Die letzte Zeile sollte man sich einprägen: Selbst wenn ein Task fehlschlägt, bleibt die HTTP-Hülle beim Polling 200 – der Fehler liegt in data.status und data.error, nicht im HTTP-Statuscode. Fehlgeschlagene Tasks werden erstattet, aber dein Code muss sie trotzdem bemerken. Verzweige nach data.status == "fail", vertraue nicht auf 200.
Verwandte Ressourcen
- Ray 3.2 Image-to-Video Modellseite – Playground, Live-Status und vollständige Preistabelle nach Auflösung
- Ray 3.2 Text-to-Video Modellseite – der Schwester-Endpoint; startet mit Text statt einem Bild (er ergänzt
durationundaspect_ratiound lässtimage_urlweg) - So verwendest du die Hailuo 2.3 Image-to-Video API – ein weiteres Image-to-Video-Tutorial auf derselben Task-API, nützlich als Schema-Vergleich
- So verwendest du die grok-imagine Image-to-Video API – ein drittes i2v-Modell zum Vergleich
- hiapi Pricing – Videopreise pro Clip nach Auflösung
Häufig gestellte Fragen
Wie lautet die Modell-ID und welchen Endpoint sollte ich aufrufen?
ray-3.2/image-to-video, eingereicht an POST https://api.hiapi.ai/v1/tasks mit den Parametern unter input. Es ist asynchron – du bekommst eine taskId zurück und pollst dann GET /v1/tasks/<id> (oder registrierst einen Callback).
Was verlangt die Eingabe?
Genau zwei Pflichtfelder – prompt (Bewegung) und image_url (öffentliche URL des Erstbilds) – plus das optionale resolution (540p/720p/1080p). Alles andere wird abgelehnt.
Warum kann ich keine Dauer oder kein Seitenverhältnis einstellen?
Die Cliplänge ist fest (ca. 5 Sekunden), daher gibt es kein duration-Feld; das Ausgabe-Seitenverhältnis wird von deinem Eingabebild übernommen, daher gibt es kein aspect_ratio-Feld. Beides zu senden liefert einen 400. Wenn du eine wählbare Dauer brauchst, verwende stattdessen Ray 3.2 Text-to-Video.
Mein Create-Aufruf gab 200 zurück, aber das Video kommt nie an. Warum?
Am häufigsten ist die image_url nicht erreichbar. Der Create-Aufruf validiert nur die Form der Anfrage, daher liefert auch ein toter Link eine taskId – und dann endet der Task mit fail. Polle bis zum terminalen Zustand und lies data.error. Stelle sicher, dass das Bild öffentlich abrufbar ist (keine Authentifizierung, kein lokaler Pfad, kein Base64).
Hat der Clip Audio? Nein – Ray 3.2 Image-to-Video liefert stumme MP4s. Füge in der Nachbearbeitung Ton hinzu oder verwende ein separates Audiomodell.
Was kostet ein Clip?
Die Abrechnung erfolgt pro Clip, und da die Länge fest ist, ist resolution der einzige Hebel – ein 1080p-Clip kostet ein Vielfaches eines 540p-Clips. Preise ändern sich, prüfe daher die Preisseite, statt Zahlen fest zu verdrahten, und prototypisiere zunächst mit 540p.
Wie lange sollte ich warten, bevor ich aufgebe?
Dutzende Sekunden bis einige Minuten sind normal. Toleriere im Polling-Loop nicht-terminale data.status-Werte und verwende großzügige Deadlines (10–15 Minuten), oder registriere einen callback mit "when": "final", damit hiapi dich benachrichtigt, statt dass du Intervalle errätst.
Ähnliche Artikel
Alle Artikel ansehen →-
Tutorial
So rufen Sie hailuo-2.3-fast/image-to-video über die HiAPI API auf: curl und Python in der Praxis
Die erste lauffähige Anfrage für die schnelle, kostengünstige Bild-zu-Video-Stufe – von der Modell-ID bis zum heruntergeladenen MP4
-
Tutorial
So rufen Sie hailuo-2.3/text-to-video über die HiAPI API auf: curl, Python und lauffähige Anfragebeispiele
-
Tutorial
Kandinsky 6.0 Pro Text-zu-Video auf HiAPI: curl- und Python-Tutorial
Eine anfrageorientierte praktische Demonstration: Aufbau des Aufrufs vom Prompt zum Videoclip, Absenden der Anfrage, Abfragen des Fertigstellungsstatus und Herunterladen des Ergebnisses