HiAPI
Blog

Tutorial

Happyhorse Image-to-Video API: Verfügbare curl- und Python-Beispiele (hiapi)

Jun 27, 2026
Happyhorse Image-to-Video API: Verfügbare curl- und Python-Beispiele (hiapi)

Du möchtest ein Standbild aus deinem eigenen Code in ein zusammenhängendes kurzes Video verwandeln. Das Modell happyhorse-1.1/image-to-video auf hiapi macht genau das: Übergib eine Bild-URL und einen optionalen Bewegungs-Prompt, und innerhalb von Sekunden erhältst du eine MP4 in 720p oder 1080p. Dieser Leitfaden ist der kürzeste funktionierende Weg – echte Request-Bodies, echte Fehlermeldungen, sowohl curl als auch Python –, damit du heute live gehen kannst, ohne mit Feldnamen zu kämpfen.

Was du vor dem Start brauchst

  • Einen hiapi-API-Key. Hole ihn dir im hiapi-Dashboard – er sieht aus wie sk-... und gehört in den Header Authorization: Bearer ....
  • Eine erreichbare Bild-URL (öffentliches HTTPS). Lokale Dateien funktionieren nicht; das Modell ruft diese URL selbst ab.
  • Eine in Worten ausgedrückte Bewegungsabsicht (optional, aber empfohlen). Je klarer die Verben, desto besser die Bewegung.

Das Modell wird pro Videosekunde abgerechnet; die Preise stehen auf der hiapi-Preisseite; der restliche Modellkatalog findest du unter hiapi models – schau dir beides an, bevor du die Stapelgröße erhöhst.

Die minimale funktionierende Anfrage (curl)

Jedes Video auf hiapi läuft über den asynchronen /v1/tasks-Endpunkt: Du POSTest zunächst eine Erstellungsanfrage, erhältst eine taskId zurück und pollst dann entweder GET /v1/tasks/{taskId} oder empfängst einen Callback bei Abschluss.

Aufgabe erstellen:

curl -X POST "https://api.hiapi.ai/v1/tasks" \
  -H "Authorization: Bearer sk-your-key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "happyhorse-1.1/image-to-video",
    "input": {
      "image_urls": ["https://example.com/your-still.jpg"],
      "prompt": "the cat slowly turns its head toward the camera and blinks",
      "resolution": "1080p",
      "duration": 5
    }
  }'

Eine erfolgreiche Antwort sieht so aus:

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

Jetzt pollst du das Ergebnis (alle 5 Sekunden reicht – die meisten Clips sind in zwei Minuten fertig):

curl "https://api.hiapi.ai/v1/tasks/tk-hiapi-01KW3D..." \
  -H "Authorization: Bearer sk-your-key"

Während der Ausführung erhältst du "status": "handling". Bei Abschluss erhältst du:

{
  "code": 200,
  "data": {
    "status": "success",
    "model": "happyhorse-1.1/image-to-video",
    "output": [
      {
        "type": "video",
        "url": "https://temp.hiapi.ai/.../01KW....mp4",
        "expireAt": 1783131245
      }
    ],
    "taskId": "tk-hiapi-01KW3D..."
  }
}

Diese url ist die, die du herunterladen musst – und zwar sofort, denn expireAt ist ein Unix-Zeitstempel, der angibt, wann der temporäre Speicher sie löscht.

Python: Pollen bis zum Erfolg

Dies ist ein vollständiges, lauffähiges Skript – trage einfach deinen Key und die Bild-URL ein.

import os
import time
import requests

API = "https://api.hiapi.ai/v1/tasks"
TOKEN = os.environ["HIAPI_KEY"]  # sk-...

def create_video(image_url: str, prompt: str, duration: int = 5,
                 resolution: str = "1080p") -> str:
    """Submit an image-to-video task. Returns the taskId."""
    r = requests.post(
        API,
        headers={"Authorization": f"Bearer {TOKEN}",
                 "Content-Type": "application/json"},
        json={
            "model": "happyhorse-1.1/image-to-video",
            "input": {
                "image_urls": [image_url],          # exactly one URL
                "prompt": prompt,                   # optional
                "resolution": resolution,           # "720p" or "1080p"
                "duration": duration,               # int, 3..15
            },
        },
        timeout=60,
    )
    body = r.json()
    if body.get("code") != 200:
        raise RuntimeError(f"create failed: {body}")
    return body["data"]["taskId"]

def wait_for_video(task_id: str, timeout_s: int = 600,
                   poll_s: int = 5) -> str:
    """Block until the task succeeds, fails, or times out. Returns the MP4 URL."""
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(
            f"{API}/{task_id}",
            headers={"Authorization": f"Bearer {TOKEN}"},
            timeout=30,
        )
        task = r.json().get("data") or {}
        status = task.get("status")
        if status == "success":
            return task["output"][0]["url"]
        if status == "fail":
            raise RuntimeError(f"task failed: {task.get('error')}")
        time.sleep(poll_s)
    raise TimeoutError(f"task {task_id} did not finish in {timeout_s}s")

def download(url: str, path: str) -> None:
    with requests.get(url, stream=True, timeout=120) as r:
        r.raise_for_status()
        with open(path, "wb") as f:
            for chunk in r.iter_content(1 << 16):
                f.write(chunk)

if __name__ == "__main__":
    task_id = create_video(
        image_url="https://example.com/your-still.jpg",
        prompt="the dancer spins once and ends in a pose",
        duration=5,
        resolution="1080p",
    )
    print("submitted:", task_id)
    mp4_url = wait_for_video(task_id)
    download(mp4_url, "out.mp4")
    print("saved out.mp4")

Das Ganze umfasst etwa 50 Zeilen und ist die gesamte produktionsreife Schnittstelle – kein SDK, keine Hintergrundwarteschlange, kein Streaming-Protokoll. Führe es mit HIAPI_KEY=sk-... python video.py aus.

Produktion: Callbacks statt Polling

Polling ist für einmalige Aufgaben und Tests in Ordnung. Für Serverlast möchtest du, dass die Plattform dich zurückruft, sobald der Clip fertig ist – das spart die Schleife, skaliert ohne zusätzlichen Aufwand, und der Request-Stream kehrt sofort zurück.

Füge auf der Stammebene des Request-Bodys (nicht innerhalb von input) ein callback-Objekt hinzu:

curl -X POST "https://api.hiapi.ai/v1/tasks" \
  -H "Authorization: Bearer sk-your-key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "happyhorse-1.1/image-to-video",
    "input": {
      "image_urls": ["https://example.com/still.jpg"],
      "prompt": "slow zoom out from the subject",
      "resolution": "1080p",
      "duration": 6
    },
    "callback": {
      "url": "https://your-app.example.com/webhooks/hiapi",
      "when": "final"
    }
  }'

Wenn die Aufgabe einen Endzustand erreicht, POSTet hiapi das Aufgabenobjekt, das du auch per Polling erhalten hättest (status, output[].url, error usw.), an deine URL. when: "final" bedeutet, dass du nur bei Erfolg oder Fehlschlag einen Callback erhältst – kein Fortschrittsrauschen.

Ein paar Regeln, die dir echte Debug-Zeit ersparen:

  • Mach deinen Webhook idempotent. Dieselbe taskId kann zweimal eintreffen (Netzwerk-Retries gibt es wirklich). Prüfe vor der Verarbeitung in deiner Datenbank, ob diese Aufgabe bereits existiert.
  • Hoste die MP4 im Handler neu. Die URL, die hiapi dir gibt, zeigt auf temporären Speicher mit einem expireAt-Feld; streame sie beim ersten Empfang in deinen eigenen Bucket, sonst verlierst du sie.
  • Bestätige schnell. Gib schnell 2xx zurück und erledige die schwere Arbeit asynchron. Langsame Webhooks riskieren Retries.
  • Nutze Polling als Fallback. Verpacke die taskId in eine verzögerte Aufgabe und polle nach 10 Minuten erneut, falls der Webhook verloren geht. Wenn du das ständig erlebst, lies unter Gründe, warum hiapi-Task-Callbacks möglicherweise nicht ausgelöst werden die häufigen Ursachen nach.

Fehlermeldungen, denen du begegnen wirst

Dies sind die exakten Strings, die die API zurückgibt; sie in deinem Code abzugleichen ist zuverlässiger, als sie zu erraten.

Situation HTTP Form des Antwort-Bodys
Falscher oder widerrufener Key 401 {"error":{"code":"permission_denied","message":"This API key cannot use the selected model...","type":"hiapi_error"}}
Pflichtfeld fehlt 400 {"code":400,"error_code":"INVALID_REQUEST","message":"invalid input: image_urls: missing required field \"image_urls\""}
resolution ungültig 400 ... resolution: value must be one of '720p', '1080p'
Dauer außerhalb des Bereichs 400 ... duration: minimum: got 2, want 3 / duration: maximum: got 30, want 15
image_urls falsche Länge 400 ... image_urls: maxItems: got 2, want 1 (oder minItems: got 0, want 1)
Unbekanntes Feld auf Stammebene oder in input 400 ... additional properties 'identity_preserving' not allowed
Aufgabe selbst schlägt nach Einreichung fehl 200 (Task-GET) mit data.status == "fail" und data.error.{code,message}

Authentifizierungs- und Validierungsfehler bedeuten, dass du nicht erneut versuchen solltest; alles andere (5xx der Plattform, Netzwerk-Timeouts während des Pollings, vorübergehende task fail mit wiederholbarem Code) eignet sich für einen Retry mit kleinem Backoff.

Ausgabe abstimmen

Es gibt vier Regler, die wirklich etwas bewirken – alles andere (seed, aspect_ratio, fps, cfg_scale, negative_prompt) wird derzeit als unbekanntes Feld abgelehnt. Bleib innerhalb dieser Menge:

  • image_urls – eine HTTPS-URL. Seitenverhältnis und Komposition der Ausgabe folgen diesem Bild.
  • prompt – eine kurze Bewegungsbeschreibung ("she smiles and tilts her head"). Filmische, verbgetriebene Prompts schlagen Adjektivlisten.
  • resolution – "720p" zum Prototyping und für Vorschaubilder, "1080p" für die Auslieferung.
  • duration – ganzzahlige Sekunden von 3 bis 15. Längere Clips kosten mehr Zeit und Geld; wähle die kürzeste Dauer, die die Geschichte erzählt.

Für Projekte, die Start- und Endbild benötigen, siehe happyhorse-1.1 reference-to-video. Für reines Text-to-Video ohne Quellbild ist happyhorse-1.1 text-to-video der Schwesterartikel. Stöbere im hiapi models catalogue oder in der Rezept-Sektion des Blogs nach dem Rest.

FAQ

Kann ich zwei Bilder übergeben, um erstes und letztes Frame zu steuern? Auf happyhorse-1.1/image-to-video nicht – image_urls hat höchstens ein Element (maxItems: 1). Wenn du das brauchst, nutze happyhorse-1.1/reference-to-video oder baue mit happyhorse-1.1 reference-to-video.

Welche Auflösung hat die MP4, die ich tatsächlich zurückbekomme? Die Ausgabe folgt dem Seitenverhältnis deines Eingabebildes und dem von dir gesetzten resolution-Flag. "1080p" liefert die höhere der beiden; "720p" ist für Tests schneller und günstiger.

Warum bekomme ich 401 permission_denied, obwohl mein Key bei anderen Modellen funktioniert? Einige Modelle sind pro Key eingeschränkt. Sieh dir die Modellseite im hiapi-Dashboard an – wenn happyhorse-1.1/image-to-video nicht auf deiner Allowlist steht, erzeuge einen neuen Key oder kontaktiere den Support mit der request_id aus dem Fehler.

Gibt es Streaming-/SSE-Antworten? Nein. Jedes Videomodell verwendet das asynchrone Aufgabenmuster aus diesem Artikel. Wähle je nachdem, ob du den Server kontrollierst, Polling oder Callback.

Wo sehe ich die tatsächlichen Kosten pro Clip? Auf der hiapi-Preisseite. Die Kosten werden pro Videosekunde berechnet, daher bestimmt die von dir übergebene duration direkt die Ausgaben – halte Entwicklungs-/Testclips bei duration: 3, um das Budget zu schonen.

Die Ausgabe-URL funktioniert nach einem Tag nicht mehr. Das ist erwartetes Verhalten – die URL ist temporärer Speicher, und die Antwort enthält einen expireAt-Unix-Zeitstempel. Lade die MP4 immer in dem Moment herunter, in dem die Aufgabe erfolgreich ist (oder streame sie über dein eigenes CDN).

Ä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