Pular para o conteúdo
Cadastre-se grátis
Referência da API

API de vídeo: cotação, geração, consulta e download

Use uma BetterToken API Key e um exemplo Python retomável para cotar, gerar de forma assíncrona, consultar e baixar vídeos MP4.

A geração de vídeo é assíncrona. Verifique as capacidades do modelo e solicite uma cotação antes de criar uma tarefa. Salve o ID, consulte o status e baixe quando o vídeo estiver pronto.

Conexão

ConfiguraçãoValor
Base URLhttps://bettertoken.ai/v1
AutenticaçãoAuthorization: Bearer YOUR_API_KEY
Listar modelos disponíveisGET /v1/video-models
Cotar antes de gerarPOST /v1/videos/quote
Criar uma tarefaPOST /v1/videos
Consultar uma tarefaGET /v1/videos/{id}
Baixar o vídeoGET /v1/videos/{id}/content
Criação compatívelPOST /v1/video/generations

Listar modelos disponíveis

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

Cada entrada do catálogo é identificada por id e operation em conjunto. Respeite seus fields, media_limits e constraints; as operações não usam todas o mesmo conjunto de parâmetros.

Configurações iniciais por família

FamíliaConfigurações do exemplo
Seedance CN5 segundos, 720p, 16:9, áudio desligado
Kling 3.05 segundos, 16:9, modo padrão; sem campo genérico de resolução
MiniMax H35 segundos, 768p, 16:9; áudio padrão do modelo
Wan 3.0 / Prime5 segundos, 720p, 16:9, áudio desligado
Wan 2.7 T2V / I2VTexto: 5 segundos, 720p, 16:9; imagem: 5 segundos, 720p, proporção da entrada

São combinações iniciais do script, não todos os limites dos modelos. Mudar de família pode exigir mais que trocar o nome. Os dez IDs exatos e as operações estão na seleção de modelos.

Fluxo completo em Python

Use Python 3 sem dependências adicionais. Defina as variáveis de ambiente, salve o código como video_example.py e execute. A cotação não inicia a geração. Digitar GENERATE cria uma tarefa real e pode gerar cobrança.

Janela do terminal
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

Use um novo VIDEO_STATE_FILE para cada tarefa nova. Ao repetir a mesma tarefa, mantenha a solicitação, a BetterToken API Key e o arquivo de estado. O script reutiliza o Idempotency-Key ou o ID salvo. O limite de espera é uma configuração do cliente, não uma promessa de duração da geração.

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")

Imagem para vídeo

Defina VIDEO_MODEL como wan-2.7-i2v para usar a imagem pública abaixo. Com VIDEO_IMAGE_URL, use sua própria imagem HTTPS autorizada e acessível ao servidor. Não envie output.aspect_ratio nessa entrada.

Imagem de entrada: caneca de cerâmica azul em fundo branco
{
"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"
}
]
}

Referências e solicitações personalizadas

Salve o JSON abaixo como request.json, defina VIDEO_REQUEST_FILE=request.json e use um novo arquivo de estado. O script cota, confirma e cria a mesma solicitação, consulta o status e baixa o resultado. As mídias devem atender aos tipos e tamanhos do modelo.

{
"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"
}
]
}

Regenerar um resultado H3

Substitua o marcador pelo ID de sua própria tarefa H3 concluída em 768p, ainda disponível para download e apta à regeneração. É uma nova tarefa paga pelo fluxo personalizado acima. Não adicione outro prompt, duração, configurações de saída ou mídias; o servidor reutiliza a tarefa original e valida as condições.

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

Identificar a conclusão

Campo ou statusAção
202Apenas aceita; o vídeo ainda não está pronto
queued / in_progressNa fila ou em execução; continue consultando
completed + delivery_status: readyPronto para download
failed / canceledPare a consulta e leia o erro; não crie outra tarefa automaticamente
delivery_status: expired / unavailableO vídeo desta tarefa não pode mais ser baixado
financial_statusA cobrança é independente da execução e do download; aguarde confirmação

Ler a cotação e a liquidação

currency informa a moeda da cotação; não a envie no JSON de geração. reserved_amount é a reserva e max_charge_amount o limite estimado. A criação recalcula e fixa a cotação da tarefa. Leia billing.currency, billing.net_amount e financial_status para a liquidação. Os valores são strings decimais; ausência ou null não significa gratuito. Consulte os preços atuais no Modelos e preços atuais.

Erros comuns

SituaçãoPróxima etapa
401Verifique a API Key; não a coloque no JSON nem em logs públicos
409Mantenha a chave de idempotência e a solicitação originais; consulte primeiro o ID conhecido
422Corrija o campo conforme o caminho e os valores permitidos do erro
410O vídeo expirou; baixe os próximos resultados antes do vencimento
429 / 503 / timeoutConsulte a tarefa existente; se a criação for incerta, reutilize a chave original sem enviar outra automaticamente