HiAPI
Blog

Tutorial

Happyhorse 1.1 Text-zu-Video API: Lauffähiges curl- und Python-Tutorial

Jun 26, 2026
Happyhorse 1.1 Text-zu-Video API: Lauffähiges curl- und Python-Tutorial

Diese Seite führt dich durch die minimale Anfrage zur Videoerzeugung mit happyhorse-1.1/text-to-video, erklärt, welche Werte jedes Eingabefeld tatsächlich akzeptiert, und zeigt zwei Wege, auf das Ergebnis zu warten (Polling und Callback). Alle folgenden Inhalte wurden gegen die Live-API getestet; Anfragestrukturen und Fehlermeldungen sind wörtlich übernommen.

Was du baust

Eine Funktion: Sie nimmt einen Text-Prompt entgegen und gibt eine herunterladbare MP4-URL zurück. Das Modell ist happyhorse-1.1/text-to-video und wird über den einheitlichen /v1/tasks-Endpunkt bereitgestellt. Du reichst eine Aufgabe ein, erhältst sofort eine taskId zurück und kannst dann entweder pollen, bis sie abgeschlossen ist, oder hiapi das Endergebnis per POST an deinen Webhook senden lassen.

Voraussetzungen:

  • Ein hiapi-API-Key. Neue Konten erhalten 1 US-Dollar Gratisguthaben – auf der API-Keys-Seite.
  • Eine Möglichkeit, HTTPS-Anfragen zu senden. Kein SDK erforderlich.

Lege den Key nicht in Client-Code ab und committe ihn nicht in git. Lies ihn aus einer Umgebungsvariable:

export HIAPI_API_KEY="sk-..."

Minimal funktionierende Anfrage

Das einzige Pflicht-Eingabefeld ist prompt. Alle anderen Felder haben Standardwerte.

curl

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "happyhorse-1.1/text-to-video",
    "input": {
      "prompt": "a calm ocean wave at sunset, slow motion, golden hour"
    }
  }'

Antwort:

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

Diese taskId ist dein Zugangsnachweis für die Aufgabe. Bewahre sie sorgfältig auf.

Python (nur Standardbibliothek)

import os
import urllib.request
import json

API_KEY = os.environ["HIAPI_API_KEY"]

def submit_task(prompt: str, **input_overrides) -> str:
    body = {
        "model": "happyhorse-1.1/text-to-video",
        "input": {"prompt": prompt, **input_overrides},
    }
    req = urllib.request.Request(
        "https://api.hiapi.ai/v1/tasks",
        method="POST",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        data=json.dumps(body).encode("utf-8"),
    )
    with urllib.request.urlopen(req) as resp:
        payload = json.loads(resp.read())
    if payload.get("code") != 200:
        raise RuntimeError(payload)
    return payload["data"]["taskId"]

task_id = submit_task("a calm ocean wave at sunset, slow motion, golden hour")
print(task_id)  # e.g. tk-hiapi-01KW23VDG2EZ20J7RQH8S67MNC

Vollständige Parametertabelle

Die Parameterstruktur ist klein und strikt – unbekannte Felder werden mit 400 abgelehnt statt stillschweigend verworfen. Hier sind die Felder innerhalb von input, die die API tatsächlich akzeptiert, direkt aus den Validierungsfehlermeldungen entnommen:

Feld Typ Erforderlich Erlaubte Werte Standardverhalten
prompt string Ja Beliebiger nicht leerer Text, der die Szene beschreibt —
aspect_ratio string Nein 16:9, 9:16, 3:4, 4:3, 4:5, 5:4, 1:1, 9:21, 21:9 Modellstandard
resolution string Nein 720p, 1080p Modellstandard
duration integer Nein 3 bis 15 (Sekunden) Modellstandard

Felder, die selbstverständlich erscheinen, aber derzeit nicht akzeptiert werden: size, audio, seed, negative_prompt. Das Senden eines dieser Felder liefert additional properties '<field>' not allowed. Wenn du andere hiapi-Modelle verwendet hast – qwen-image-2.0 nutzt size, z-image nutzt aspect_ratio – beachte, dass happyhorse-1.1/text-to-video zur Familie der Seitenverhältnisse gehört, nicht zur Familie expliziter Größen.

Eine Anfrage mit allen unterstützten Parametern:

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "happyhorse-1.1/text-to-video",
    "input": {
      "prompt": "a vintage paper plane gliding above neon Tokyo at night, cinematic, 24fps",
      "aspect_ratio": "16:9",
      "resolution": "1080p",
      "duration": 8
    }
  }'

Zu Kosten und Dauer

Das Modell wird nach den Sekunden des erzeugten Videos abgerechnet – die aktuellen Preise findest du auf der Preisseite. duration ist der Kostenhebel, resolution nicht. Von 5 auf 15 Sekunden zu gehen verdreifacht die Kosten; beim Wechsel von 720p auf 1080p ist das zum Zeitpunkt dieses Artikels nicht der Fall. Verwende während der Entwicklung standardmäßig duration: 3 und eine niedrigere Auflösung; wechsle erst bei endgültig bestätigten Prompts auf 1080p.

Ergebnisse abrufen: Polling

Der einfachste Weg, die Ausgabe zu erhalten, ist ein GET auf die Aufgabe per id, bis status einen Endzustand erreicht.

import time
import urllib.request
import json

def get_task(task_id: str) -> dict:
    req = urllib.request.Request(
        f"https://api.hiapi.ai/v1/tasks/{task_id}",
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())["data"]

def wait_for_task(task_id: str, timeout_s: int = 600, poll_interval_s: int = 10) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        task = get_task(task_id)
        status = task["status"]
        if status == "success":
            return task
        if status == "fail":
            raise RuntimeError(f"task failed: {task}")
        time.sleep(poll_interval_s)
    raise TimeoutError(f"task {task_id} still {status} after {timeout_s}s")

task = wait_for_task(submit_task("a quiet coffee shop on a rainy afternoon"))
video_url = task["output"][0]["url"]
print(video_url)

Einige wichtige Details:

  • status durchläuft nacheinander queued → handling → archiving → success (oder fail). Jeder Status vor success oder fail darf nicht als Endzustand betrachtet werden.
  • Die Generierungszeit wächst mit duration und der aktuellen Warteschlangentiefe. Ein 5-Sekunden-720p-Auftrag ist normalerweise in wenigen Minuten fertig; für 1080p mit 15 Sekunden solltest du mehr Zeit einplanen.
  • output[].url liegt im temporären Speicher. Lade die Bytes sofort herunter und hoste sie selbst neu; speichere nicht nur die URL und liefere sie an Endnutzer aus – sie läuft ab.
import urllib.request

def download(video_url: str, out_path: str) -> None:
    with urllib.request.urlopen(video_url) as resp, open(out_path, "wb") as f:
        f.write(resp.read())

download(video_url, "ocean.mp4")

Ergebnisse abrufen: Callback (Produktionsmodus)

Polling eignet sich für Skripte und Notebooks, aber in einem echten Backend möchtest du nicht, dass ein Worker für jedes Video mehrere Minuten blockiert ist. Übergib beim Einreichen ein callback-Objekt, und hiapi sendet den abgeschlossenen Aufgabentext genau einmal per POST an deine URL.

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "happyhorse-1.1/text-to-video",
    "input": {
      "prompt": "an astronaut planting a flag on a glass beach",
      "aspect_ratio": "16:9",
      "resolution": "1080p",
      "duration": 6
    },
    "callback": {
      "url": "https://example.com/hooks/hiapi",
      "when": "final"
    }
  }'

Derzeit wird nur when: "final" unterstützt – das Senden eines anderen Werts liefert:

{ "code": 400, "error_code": "INVALID_REQUEST",
  "message": "invalid callback.when: only 'final' is supported" }

Einen robusten Empfänger entwerfen

Drei Regeln machen den Callback-Pfad sicher:

  1. Idempotenz. Persistiere nach taskId. Wenn du denselben Callback zweimal erhältst – was wegen Wiederholungen früher oder später passiert –, muss die wiederholte Verarbeitung ein No-Op sein.
  2. Bestätigung von Verarbeitung entkoppeln. Sobald die Payload in die Warteschlange gestellt ist, gib vom Webhook 200 OK zurück. Das eigentliche Herunterladen / erneute Hochladen / Schreiben in die Datenbank gehört in einen Hintergrundtask. Langsame Webhooks provozieren Timeouts und doppelte Zustellungen.
  3. Auf Polling zurückfallen. Speichere jede von dir eingereichte taskId zusammen mit created_at in deiner eigenen Datenbank. Betreibe einen Sweeper, der für jede Zeile, die seit etwa 30 Minuten keinen Endzustand hat, /v1/tasks/{id} abfragt. Der Callback ist der schnelle Pfad, nicht der einzige.

Ein minimaler FastAPI-Empfänger:

from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/hooks/hiapi")
async def hiapi_callback(req: Request):
    body = await req.json()
    task = body.get("data", body)  # callback wraps the same shape as GET
    task_id = task["taskId"]
    if task["status"] == "success":
        url = task["output"][0]["url"]
        # enqueue, don't download here
        await queue.put({"task_id": task_id, "url": url})
    return {"ok": True}

Fehler, die dir tatsächlich begegnen

Die Form des Fehler-Envelopes ist immer gleich – {code, error_code, message, data} –, sodass du nur einen Handler schreiben musst. Diese verdienen bei diesem Modell eine besondere Behandlung:

HTTP error_code Auslöser Behebung
400 INVALID_REQUEST Unbekanntes Feld, falscher Enum-Wert, duration außerhalb von 3–15 Sieh dir message an, sie nennt das Feld – z. B. aspect_ratio: value must be one of '16:9'...
401 — Key fehlt oder ist falsch, oder permission_denied Prüfe den Header Authorization: Bearer sk-... erneut
402 — Guthaben reicht nicht für die geschätzten Kosten Lade unter hiapi.ai/en/pricing auf und versuche es erneut
429 — Du hämmerst auf den Endpunkt ein Backoff mit Jitter verwenden und erneut versuchen

Eine echte Validierungsfehlermeldung sieht so aus – kopierbar als dein Test-Fixture:

{
  "code": 400,
  "data": null,
  "error_code": "INVALID_REQUEST",
  "message": "invalid input: aspect_ratio: value must be one of '16:9', '9:16', '3:4', '4:3', '4:5', '5:4', '1:1', '9:21', '21:9'; resolution: value must be one of '720p', '1080p'; duration: maximum: got 99, want 15"
}

Beachte, dass jedes Feld in derselben Antwort gemeldet wird. Validiere lokal, wo du kannst, aber verlass dich darauf, wenn du es nicht kannst.

Verwandte Inhalte auf hiapi

Häufig gestellte Fragen

Erzeugt happyhorse-1.1/text-to-video Audio? Über die Task-API nicht – die Anfragestruktur hat derzeit kein audio-Feld, und das Senden liefert additional properties 'audio' not allowed. Die Ausgabe ist ein stummes MP4. Lege die Tonspur nachgelagert darüber.

Welche Seitenverhältnisse stehen für Hochformat / Kurzvideo zur Verfügung? Für Ausgaben im TikTok- und Reels-Stil ist 9:16 die Standardwahl. Die API akzeptiert außerdem 4:5 (Hochformat für den Instagram-Feed) und 9:21 (extra-hohes Hochformat), falls du ein schmaleres Bildformat brauchst.

Was sind die kürzeste und längste Dauer? duration ist eine ganze Zahl von 3 bis 15 Sekunden (einschließlich der Endpunkte). Das Senden von 0 liefert duration: minimum: got 0, want 3; das Senden von 99 liefert duration: maximum: got 99, want 15. Wähle den kleinsten Wert, der die benötigte Bewegung einfängt – die Dauer ist das, wofür du bezahlst.

Kann ich seed übergeben, um reproduzierbare Ausgaben zu erhalten? Das Modell unterstützt das derzeit nicht. Die Anfrage {"input": {"prompt": "...", "seed": 42}} schlägt mit additional properties 'seed' not allowed fehl. Wenn Reproduzierbarkeit wichtig ist, protokolliere den vollständigen Request-Body und versuche es mit demselben Prompt erneut; du erhältst ähnliche – aber nicht pixelgenau identische – Ergebnisse.

Wird negative_prompt unterstützt? Nein. Lenke die Ausgabe, indem du Einschränkungen direkt im prompt ergänzt (z. B. "...clean composition, no text, no watermarks").

Wie lange ist die Ausgabe-URL gültig? Bei Standardplänen ist das Feld storage der Aufgabe temp, und output[].url ist ein kurzlebiger signierter Link. Lade die Datei herunter und hoste sie neu, sobald die Aufgabe success erreicht. Speichere nicht die Original-URL, um sie später bereitzustellen.

Wie kontrolliere ich die Kosten während der Entwicklung? Zwei Spargewohnheiten: Behalte duration: 3, bis der Prompt feststeht, und reiche mit resolution: "720p" ein, bevor das Storyboard bestätigt ist. Erst beim finalen Rendern auf 1080p und längere Dauer hochgehen.

Polling oder Callback – was sollte ich verwenden? Beides, in dieser Reihenfolge. Zuerst Polling, weil es ein bewegliches Teil weniger gibt. Wechsle zum Callback, sobald du zum ersten Mal ein Backend hast, in dem du es dir nicht leisten kannst, einen Worker in wait_for_task blockieren zu lassen. Behalte Polling in jedem Fall als Fallback-Sweeper; der Callback ist Best Effort, keine garantierte Zustellung.

Warum erhalte ich 402, obwohl ich Guthaben habe, um den Aufruf zu machen? Die API schätzt die Obergrenze der Aufgabenkosten – Dauer × Preis pro Sekunde – und reserviert sie im Voraus. Wenn das Guthaben diese Schätzung nicht abdeckt, wird sofort 402 zurückgegeben. Lade auf, versuche es erneut; bei Erfolg wird nur der tatsächliche Preis abgebucht.

Ä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