Tutorial
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:
statusdurchläuft nacheinanderqueued → handling → archiving → success(oderfail). Jeder Status vorsuccessoderfaildarf nicht als Endzustand betrachtet werden.- Die Generierungszeit wächst mit
durationund 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[].urlliegt 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:
- 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. - Bestätigung von Verarbeitung entkoppeln. Sobald die Payload in die Warteschlange gestellt ist, gib vom Webhook
200 OKzurück. Das eigentliche Herunterladen / erneute Hochladen / Schreiben in die Datenbank gehört in einen Hintergrundtask. Langsame Webhooks provozieren Timeouts und doppelte Zustellungen. - Auf Polling zurückfallen. Speichere jede von dir eingereichte
taskIdzusammen mitcreated_atin 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
- Modellseite von happyhorse-1.1/text-to-video – Playground, vollständige Parameter-UI und Live-Preise
- happyhorse-1.1/image-to-video – dieselbe Modellfamilie, aber um statische Bilder in Bewegung zu bringen
- veo-3.1-fast/text-to-video und seedance-2.0 – alternative Text-zu-Video-Modelle auf derselben
/v1/tasks-API - Referenz zur einheitlichen Task-API – maßgebliche Quelle für
POST /v1/tasks,GET /v1/tasks/:id, Callbacks und den Fehler-Envelope - Alle Modelle durchstöbern und Preise
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 →-
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