Zum Inhalt springen
Kostenlos registrieren
API-Referenz

Video-API: Kosten prüfen, erzeugen, abfragen und laden

Nutze einen BetterToken API Key und ein fortsetzbares Python-Beispiel für Kostenschätzung, asynchrone Erzeugung, Statusabfragen und MP4-Download.

Videos werden asynchron erzeugt. Prüfe die Modellfähigkeiten und hole eine Kostenschätzung ein. Erstelle dann die Aufgabe, speichere ihre ID und frage den Status ab. Lade das fertige Video herunter.

Verbindungsdaten

EinstellungWert
Base URLhttps://bettertoken.ai/v1
AuthentifizierungAuthorization: Bearer YOUR_API_KEY
Verfügbare Modelle auflistenGET /v1/video-models
Kosten vorab schätzenPOST /v1/videos/quote
Aufgabe erstellenPOST /v1/videos
Aufgabe abfragenGET /v1/videos/{id}
Video herunterladenGET /v1/videos/{id}/content
Kompatibler ErstellungsendpunktPOST /v1/video/generations

Verfügbare Modelle auflisten

Terminal-Fenster
export BETTERTOKEN_API_KEY="YOUR_API_KEY"
curl --fail-with-body "https://bettertoken.ai/v1/video-models" \
-H "Authorization: Bearer $BETTERTOKEN_API_KEY"

Jeder Katalogeintrag wird durch id und operation gemeinsam bestimmt. Beachte seine fields, media_limits und constraints. Ein fester Parametersatz passt nicht zu allen Operationen.

Startwerte nach Modellfamilie

FamilieEinstellungen im Beispiel
Seedance CN5 Sekunden, 720p, 16:9, Audio aus
Kling 3.05 Sekunden, 16:9, Standardmodus; kein allgemeines Auflösungsfeld
MiniMax H35 Sekunden, 768p, 16:9; Audio nach Modellstandard
Wan 3.0 / Prime5 Sekunden, 720p, 16:9, Audio aus
Wan 2.7 T2V / I2VText: 5 Sekunden, 720p, 16:9; Bild: 5 Sekunden, 720p, Verhältnis nach Eingabe

Das sind Startwerte für das Skript, nicht sämtliche Modellgrenzen. Beim Familienwechsel reicht ein anderer Modellname nicht immer aus. Die zehn genauen IDs und Operationen stehen in der Modellauswahl.

Vollständiger Python-Ablauf

Verwende Python 3 ohne zusätzliche Abhängigkeiten. Setze die Umgebungsvariablen, speichere den Code als video_example.py und führe ihn aus. Eine Schätzung startet keine Generierung. Die Eingabe GENERATE erstellt eine echte Aufgabe und kann Kosten verursachen.

Terminal-Fenster
export BETTERTOKEN_API_KEY="YOUR_API_KEY"
export VIDEO_MODEL="wan-2.7-t2v"
export VIDEO_STATE_FILE="video-state.json"
python3 video_example.py

Nutze für eine neue Aufgabe eine neue VIDEO_STATE_FILE. Behalte bei Wiederholungen derselben Aufgabe Anfrage, BetterToken API Key und Statusdatei bei. Das Skript verwendet den Idempotency-Key oder die gespeicherte Aufgaben-ID erneut. Die Wartefrist ist eine Clienteinstellung, keine Zusage zur Generierungsdauer.

import hashlib
import json
import os
import shutil
import time
import uuid
from pathlib import Path
from urllib.error import HTTPError
from urllib.request import Request, urlopen
base_url = "https://bettertoken.ai/v1"
api_key = os.environ["BETTERTOKEN_API_KEY"]
model = os.getenv("VIDEO_MODEL", "wan-2.7-t2v")
state_path = Path(os.getenv("VIDEO_STATE_FILE", "video-state.json"))
sample_image = "https://docs.bettertoken.ai/images/nano-banana-guide/sample-07.png"
body = {
"model": "wan-2.7-t2v",
"operation": "text_to_video",
"prompt": "A blue ceramic mug on a white studio table. The camera slowly moves closer. Soft daylight.",
"duration": {
"mode": "fixed",
"seconds": 5
},
"output": {
"resolution": "720p",
"aspect_ratio": "16:9",
"format": "mp4"
}
}
seedance_models = {
"seedance-2.5-cn", "seedance-2.0-cn",
"seedance-2.0-fast-cn", "seedance-2.0-mini-cn",
}
allowed_models = seedance_models | {
"kling-3.0", "minimax-h3", "wan-3.0", "wan-3.0-prime",
"wan-2.7-t2v", "wan-2.7-i2v",
}
if model not in allowed_models:
raise ValueError("Choose a Model ID from the documented batch")
body["model"] = model
if model in seedance_models or model in {"wan-3.0", "wan-3.0-prime"}:
body["audio"] = {"mode": "off"}
elif model == "kling-3.0":
body["output"].pop("resolution")
body["audio"] = {"mode": "off"}
body["provider_options"] = {"kling": {"mode": "std"}}
elif model == "minimax-h3":
body["output"]["resolution"] = "768p"
elif model == "wan-2.7-i2v":
body["operation"] = "image_to_video"
body["prompt"] = "The camera slowly moves around the blue mug. Preserve its shape and the background."
body["output"].pop("aspect_ratio")
body["media"] = [{
"id": "first-frame", "type": "image", "role": "first_frame",
"source": os.getenv("VIDEO_IMAGE_URL", sample_image),
}]
request_file = os.getenv("VIDEO_REQUEST_FILE")
if request_file:
body = json.loads(Path(request_file).read_text(encoding="utf-8"))
def api(method, path, payload=None, extra_headers=None):
headers = {"Authorization": f"Bearer {api_key}"}
headers.update(extra_headers or {})
data = None
if payload is not None:
data = json.dumps(payload).encode("utf-8")
headers["Content-Type"] = "application/json"
request = Request(base_url + path, data=data, headers=headers, method=method)
try:
with urlopen(request, timeout=60) as response:
return json.load(response)
except HTTPError as error:
message = error.read().decode("utf-8", errors="replace")
raise RuntimeError(f"HTTP {error.code}: {message}") from error
digest = hashlib.sha256(json.dumps(body, sort_keys=True).encode("utf-8")).hexdigest()
if state_path.exists():
state = json.loads(state_path.read_text(encoding="utf-8"))
if state["request_sha256"] != digest:
raise ValueError("Request changed. Use a new VIDEO_STATE_FILE for a new task")
else:
state = {"request_sha256": digest, "idempotency_key": str(uuid.uuid4())}
state_path.write_text(json.dumps(state), encoding="utf-8")
task_id = state.get("task_id")
if not task_id:
if not state.get("create_attempted"):
catalogue = api("GET", "/video-models")
if not any(row["id"] == body["model"] and row["operation"] == body["operation"]
for row in catalogue["data"]):
raise RuntimeError("This model and operation are not in the current catalogue")
quote = api("POST", "/videos/quote", body)
print(json.dumps(quote, ensure_ascii=False, indent=2))
if input("Review the quote. Type GENERATE to create a paid task: ") != "GENERATE":
raise SystemExit("No generation request sent")
if time.time() >= quote["expires_at"]:
raise RuntimeError("Quote expired. Re-run to request a new quote")
state["create_attempted"] = True
state_path.write_text(json.dumps(state), encoding="utf-8")
task = api("POST", "/videos", body, {"Idempotency-Key": state["idempotency_key"]})
task_id = task["id"]
state["task_id"] = task_id
state_path.write_text(json.dumps(state), encoding="utf-8")
print("Task ID:", task_id)
deadline = time.monotonic() + 1800
while time.monotonic() < deadline:
task = api("GET", f"/videos/{task_id}")
print(task["status"], task["stage"], task["delivery_status"], task["financial_status"])
if task["status"] in {"failed", "canceled"}:
raise RuntimeError(json.dumps(task.get("error"), ensure_ascii=False))
if task["delivery_status"] in {"expired", "unavailable"}:
raise RuntimeError("Video download is no longer available")
if task["status"] == "completed" and task["delivery_status"] == "ready":
print(json.dumps(task["billing"], ensure_ascii=False, indent=2))
break
time.sleep(5)
else:
raise TimeoutError("Task still pending. Re-run with the same state file to resume")
request = Request(base_url + f"/videos/{task_id}/content",
headers={"Authorization": f"Bearer {api_key}"})
with urlopen(request, timeout=120) as response:
if response.headers.get_content_type() != "video/mp4":
raise RuntimeError("Expected an MP4 response; inspect the task before retrying")
with Path("video.mp4").open("wb") as output:
shutil.copyfileobj(response, output)
print("Saved video.mp4")

Video aus einem Bild

Setze VIDEO_MODEL auf wan-2.7-i2v, um das öffentliche Beispielbild unten zu verwenden. Mit VIDEO_IMAGE_URL kannst du ein eigenes zulässiges HTTPS-Bild wählen, das der Server abrufen kann. Übergib hier kein output.aspect_ratio.

Bildquelle für Video: blaue Keramiktasse vor weißem Hintergrund
{
"model": "wan-2.7-i2v",
"operation": "image_to_video",
"prompt": "The camera slowly moves around the blue mug. Preserve the mug shape and the white background.",
"duration": {
"mode": "fixed",
"seconds": 5
},
"output": {
"resolution": "720p",
"format": "mp4"
},
"media": [
{
"id": "first-frame",
"type": "image",
"role": "first_frame",
"source": "https://docs.bettertoken.ai/images/nano-banana-guide/sample-07.png"
}
]
}

Referenzen und eigene Anfragen

Speichere das JSON unten als request.json, setze VIDEO_REQUEST_FILE=request.json und nutze eine neue Statusdatei. Das Skript schätzt und bestätigt die Kosten, erstellt dieselbe Anfrage, fragt ihren Status ab und lädt das Ergebnis. Medien müssen den Typ- und Größenvorgaben des Modells entsprechen.

{
"model": "seedance-2.5-cn",
"operation": "reference_to_video",
"prompt": "Create a studio product video of the blue mug in the reference image. Keep the mug design consistent.",
"duration": {
"mode": "fixed",
"seconds": 5
},
"output": {
"resolution": "720p",
"aspect_ratio": "16:9",
"format": "mp4"
},
"audio": {
"mode": "off"
},
"media": [
{
"id": "reference-1",
"type": "image",
"role": "reference_image",
"source": "https://docs.bettertoken.ai/images/nano-banana-guide/sample-07.png"
}
]
}

H3-Ergebnis neu generieren

Ersetze den Platzhalter durch die ID deiner abgeschlossenen, noch herunterladbaren 768p-H3-Aufgabe, die die Voraussetzungen erfüllt. Das ist eine neue kostenpflichtige Aufgabe über den eigenen Anfrageablauf oben. Füge keinen neuen Prompt, keine Dauer, Ausgabeeinstellungen oder Medien hinzu. Der Server übernimmt die ursprüngliche Aufgabe und prüft die Bedingungen.

{
"model": "minimax-h3",
"operation": "video_edit",
"provider_options": {
"hailuo": {
"source_task_id": "YOUR_COMPLETED_H3_TASK_ID"
}
}
}

Abschluss erkennen

Feld oder StatusAktion
202Nur angenommen; das Video ist noch nicht fertig
queued / in_progressIn Warteschlange oder Ausführung; weiter abfragen
completed + delivery_status: readyZum Download bereit
failed / canceledAbfragen beenden und Fehler lesen; nicht automatisch neu erstellen
delivery_status: expired / unavailableDas Video dieser Aufgabe kann nicht heruntergeladen werden
financial_statusAbrechnung ist von Ausführung und Download getrennt; Bestätigung abwarten

Schätzung und Abrechnung lesen

currency ist die Währung der Schätzung; übergib sie nicht im Generierungs-JSON. reserved_amount ist die Reservierung und max_charge_amount die geschätzte Obergrenze. Beim Erstellen werden die Kosten erneut berechnet und für die Aufgabe festgehalten. Lies billing.currency, billing.net_amount und financial_status für die Abrechnung. Beträge sind Dezimalzeichenfolgen; fehlend oder null bedeutet nicht kostenlos. Aktuelle Preise stehen im Aktuelle Modelle und Preise.

Häufige Fehler

SituationNächster Schritt
401API Key prüfen; nicht in JSON oder öffentliche Logs schreiben
409Ursprünglichen Idempotenzschlüssel und Anfrage behalten; bekannte Aufgaben-ID zuerst abfragen
422Feld anhand des Fehlerpfads und der erlaubten Werte korrigieren
410Video abgelaufen; künftige Ergebnisse vor Ablauf laden
429 / 503 / timeoutBestehende Aufgabe zuerst abfragen; bei unklarer Erstellung den ursprünglichen Schlüssel statt automatisch einen neuen verwenden