HiAPI
Blog

Tutorial

HappyHorse 1.1 Referenz-zu-Video API: direkt ausführbares curl + Python Tutorial

Jun 26, 2026
HappyHorse 1.1 Referenz-zu-Video API: direkt ausführbares curl + Python Tutorial

HappyHorse 1.1 R2V verwandelt bis zu neun Referenzbilder plus einen Text-Prompt in ein kurzes Video und sorgt dabei über mehrere Einstellungen hinweg für konsistentes Subjekt, konsistente Kleidung und konsistenten Szenenstil. Auf HiAPI wird dieses Modell über einen einheitlichen asynchronen Task-Endpunkt bereitgestellt; die zugehörige Modell-ID lautet happyhorse-1.1/reference-to-video. Dieses Tutorial zeigt zuerst die minimal lauffähige Anfrage und danach eine produktionsreife Version mit Callback.

Ziel und Voraussetzungen

Du wirst Folgendes umsetzen:

  1. 1-9 Referenzbilder auf HTTPS-URLs hochladen (oder bereits hosten), die für die API erreichbar sind.
  2. Eine Task an https://api.hiapi.ai/v1/tasks per POST senden, die auf diese URLs verweist.
  3. Sobald die Task den Status success erreicht, die endgültige MP4-Adresse aus data.output[0].url auslesen.

Voraussetzungen:

  • Ein HiAPI-Konto und ein API-Key, der mit sk- beginnt. Du erhältst ihn in der HiAPI-Konsole; die Regeln zu Request-Headern findest du in der Dokumentation zur Authentifizierung. Der Schlüssel muss als Authorization: Bearer sk-... übergeben werden und darf niemals in Client-Code stehen.
  • Anforderungen an das Bild-Hosting: Jedes Referenzbild muss im Format JPEG, PNG oder WEBP vorliegen, die kürzeste Seite ≥ 400px und ≤ 20MB betragen. Öffentliche CDN-URLs sind zulässig; signierte S3-URLs ebenfalls, solange der Task-Ausführer darauf zugreifen kann.

Minimal lauffähige Anfrage (curl)

Dies ist der kleinste Request-Body, den das Schema akzeptiert. Er nutzt die Standardausgabe 1080p / 16:9 / 5 Sekunden, daher musst du nur die Felder prompt und reference_image setzen.

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/reference-to-video",
    "input": {
      "prompt": "Die Figur aus [Image 1] läuft durch eine Szene im Stil von [Image 2], filmische Kameraführung",
      "reference_image": [
        "https://static.hiapi.ai/example/ref-1.jpg",
        "https://static.hiapi.ai/example/ref-2.jpg"
      ]
    }
  }'

Die Antwort ist der Task-Umschlag; das einzige Feld, das du tatsächlich brauchst, ist data.taskId:

{
  "code": 200,
  "data": { "taskId": "task_xxx", "status": "queued" }
}

Polling bis zum Endzustand:

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

Wenn data.status zu success wird, liegt die Video-URL in data.output[0].url. Diese URL trägt einen expireAt-Zeitstempel; lade die Datei daher sofort herunter oder übertrage sie in persistenten Speicher – verlinke die Adresse nicht direkt (Hotlinking) auf deiner Website.

Parameter

Der R2V-Endpunkt validiert Eingaben streng; unbekannte Felder führen zu 400 INVALID_REQUEST. Das vollständige Schema (live gegen POST /v1/tasks verifiziert):

Feld Typ Erforderlich Zulässige Werte Standardwert
model string Ja happyhorse-1.1/reference-to-video (fester Wert) -
input.prompt string Ja Referenzbilder mit [Image 1], [Image 2]…… referenzieren, in derselben Reihenfolge wie reference_image -
input.reference_image string[] Ja 1-9 HTTPS-URLs, Format JPEG/PNG/WEBP, kürzeste Seite ≥ 400px, jedes ≤ 20MB -
input.resolution enum Nein 720p, 1080p 1080p
input.aspect_ratio enum Nein 16:9, 9:16, 3:4, 4:3, 4:5, 5:4, 1:1, 9:21, 21:9 16:9
input.duration integer Nein 3-15 (Sekunden) 5
callback.url string Nein HTTPS-URL, die Endzustandsereignisse der Task empfängt -
callback.when enum Nein final (derzeit einziger Wert) final

Einige weniger intuitive Validierungsregeln, die du kennen solltest, bevor sie dir Ärger bereiten:

  • Übergib reference_image auch dann als Array, wenn es nur eine Referenzbild-URL gibt. Ein direkter String führt zu reference_image: got string, want array.
  • duration muss eine Ganzzahl zwischen 3 und 15 (einschließlich) sein. 999 und 1 liefern beide eine klare Grenzwert-Fehlermeldung.
  • Auf oberster Ebene gibt es kein seed, negative_prompt, fps, cfg_scale oder steps. Die API lehnt unbekannte Eigenschaften ab.

Die vollständige Preisgestaltung nach Kombinationen aus Auflösung und Dauer findest du auf der HiAPI-Preisseite – die Kosten variieren mit beiden Parametern resolution und duration.

Python: End-to-End + Polling

Der folgende Codeblock ist ein eigenständiger Generator, der eine Task einreicht, bis zum Endzustand pollt und das Video herunterlädt. Er ist bewusst abhängigkeitsfrei (nur requests), damit du ihn in jeden Service kopieren kannst. Eine ausführlichere Beschreibung des Request-Lebenszyklus findest du im Quickstart.

import os
import time
import requests

API = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"}


def submit_r2v(prompt: str, reference_images: list[str], duration: int = 5,
               resolution: str = "1080p", aspect_ratio: str = "16:9") -> str:
    if not 1 <= len(reference_images) <= 9:
        raise ValueError("reference_image must contain 1-9 URLs")
    payload = {
        "model": "happyhorse-1.1/reference-to-video",
        "input": {
            "prompt": prompt,
            "reference_image": reference_images,
            "resolution": resolution,
            "aspect_ratio": aspect_ratio,
            "duration": duration,
        },
    }
    r = requests.post(API, json=payload, headers=HEADERS, timeout=60)
    r.raise_for_status()
    data = r.json()
    if data.get("code") != 200:
        raise RuntimeError(f"create failed: {data}")
    return data["data"]["taskId"]


def wait_for_video(task_id: str, timeout_s: int = 600, poll_s: int = 5) -> str:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(f"{API}/{task_id}", headers=HEADERS, timeout=30)
        r.raise_for_status()
        task = r.json()["data"]
        status = task["status"]
        if status == "success":
            return task["output"][0]["url"]
        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} did not finish in {timeout_s}s")


def download(url: str, path: str) -> None:
    with requests.get(url, stream=True, timeout=300) 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 = submit_r2v(
        prompt="Behalte das Subjekt-Design aus [Image 1] bei, es läuft in der Atmosphäre von [Image 2]",
        reference_images=[
            "https://static.hiapi.ai/example/char.jpg",
            "https://static.hiapi.ai/example/mood.jpg",
        ],
        duration=5,
        resolution="720p",
        aspect_ratio="9:16",
    )
    print("submitted", task_id)
    video_url = wait_for_video(task_id)
    print("ready", video_url)
    download(video_url, "out.mp4")

Dieser Code tut bewusst Folgendes:

  • Er liest den Schlüssel aus der Umgebungsvariable HIAPI_API_KEY, sodass er niemals in git oder Container-Logs landet.
  • Er nutzt r.raise_for_status(), um Transportfehler zu erfassen, bevor JSON geparst wird.
  • Er betrachtet nur success und fail als Endzustände – alle anderen Status (queued, processing usw.) lassen die Schleife weiterlaufen.
  • Er lädt die Ausgabe-URL sofort herunter. Ausgabe-URLs sind signierte Adressen und laufen ab; wenn du die Datei später brauchst, übertrage sie in persistenten Speicher oder kopiere sie in deinen eigenen Bucket.

Produktion: von Polling auf Webhook umstellen

Alle 5 Sekunden zu pollen ist für ein Notebook oder einen Worker mit geringem Traffic in Ordnung. Für echte Services registriere beim Erstellen der Task einen Callback, damit HiAPI Endzustandsereignisse an deinen Endpunkt pusht – so bleiben deine Worker untätig und du zahlst nicht mehr für leere GET-Anfragen. Das vollständige Request-Schema findest du unter Task erstellen, das ergänzende Polling unter Task-Details abrufen.

payload = {
    "model": "happyhorse-1.1/reference-to-video",
    "input": {
        "prompt": "Die Figur aus [Image 1] läuft durch die Stadtstraße aus [Image 2]",
        "reference_image": [
            "https://cdn.example.com/char.jpg",
            "https://cdn.example.com/street.jpg",
        ],
        "resolution": "1080p",
        "aspect_ratio": "16:9",
        "duration": 8,
    },
    "callback": {
        "url": "https://your-domain.com/hiapi/callback",
        "when": "final",
    },
}

Der empfangende Endpunkt muss zwei Dinge berücksichtigen:

  1. Idempotenz nach taskId. success und fail sind beides Endzustände; HiAPIs Wiederholungen (oder Duplikate aus deiner eigenen Retry-Schicht) stellen dieselbe taskId erneut zu. Dedupliziere, bevor du Nebenwirkungen auslöst (S3-Kopie, Slack-Nachricht, Abrechnungsereignis).
  2. Ergänzendes Abgleich-Polling. Wenn dein Endpunkt ausfällt, gehen Callbacks verloren. Halte eine leichte Abgleich-Task bereit, die für alle Tasks, die länger als die worst-case-Generierungszeit im Status processing hängen, GET /v1/tasks/<id> aufruft.

Fehler, die du tatsächlich antreffen wirst

Diese solltest du explizit behandeln; alles andere sind vorübergehende 5xx-Fehler, die das SDK oder dein HTTP-Client mit Backoff wiederholen sollte.

  • 401 permission_denied – Der Authorization-Header fehlt, ist falsch formatiert oder der Schlüssel wurde widerrufen. Fordere in der Konsole einen neuen Schlüssel an, statt den Header zu reparieren.
  • 400 INVALID_REQUEST mit Feldpfad – Die Fehlermeldung sagt dir genau, was fehlgeschlagen ist, z. B. reference_image: maxItems: got 10, want 9 oder aspect_ratio: value must be one of .... Validiere Eingaben, bevor dein Worker sie einreicht, damit du lokal schnell fehlschlägst.
  • task failed (Endzustand) – Polling/Callback liefert status: "fail" samt error.code und error.message. Häufige Ursachen: Referenzbild-URL, die der Worker nicht abrufen kann, ausgelöste NSFW-Prüfung oder ein Bild, dessen kürzeste Seite unter dem Schwellenwert von 400px liegt.

Häufige Fragen

Kann ich mehr als 9 Bilder referenzieren? Nein. Das Schema beschränkt reference_image auf maximal 9. Wenn du mehr Variationen brauchst, kombiniere außerhalb der Plattform ein dichteres Referenzbild (Rasterbild oder Storyboard) und verwende es als einzelne Eingabe.

Müssen im Prompt die [Image N]-Marker verwendet werden? Diese Marker sind deine Möglichkeit, auf bestimmte Referenzbilder zu verweisen. Ein Prompt ohne jegliche Bildangabe ist ebenfalls zulässig, aber das Modell mischt dann alle Referenzbilder frei – für Szenarien mit Multi-Shot-Konsistenz ist das meist nicht das gewünschte Ergebnis.

Warum erhalte ich 503 nsfw_moderation_unavailable? Dies ist ein vorübergehender Plattformfehler des Moderationsdienstes und hat nichts mit deiner Anfrage zu tun. Wiederhole die Anfrage nach kurzem Backoff.

Sollte ich 720p oder 1080p verwenden? 1080p ist der Standard und hat die bessere Bildqualität, ist aber teurer und langsamer beim Rendern. Für vertikale Social-Media-Clips, bei denen der Player ohnehin heruntersampelt, ist 720p mit aspect_ratio: 9:16 meist der bessere Kompromiss.

Kann ich seed übergeben, um Ausgaben reproduzierbar zu machen? Derzeit nicht. Die API betrachtet seed als unbekannte Eigenschaft und lehnt sie ab. Wenn du Reproduzierbarkeit brauchst, lege prompt, reference_image, aspect_ratio, resolution und duration exakt fest und akzeptiere eine gewisse Abweichung zwischen den Läufen.

Wo liegen die Ausgabe-URLs und wie lange sind sie gültig? Es sind signierte URLs mit kurzer TTL; die Antwort enthält expireAt. Lade die Datei nach erfolgreicher Task sofort herunter oder übertrage sie in persistenten Speicher.

Ä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