Tutorial
API de texto para vídeo Happyhorse 1.1: tutorial executável de curl e Python
Esta página apresenta a requisição mínima para gerar um vídeo com happyhorse-1.1/text-to-video, explica o que cada campo de entrada aceita na prática e mostra as duas formas de esperar pelo resultado (polling e callback). Todo o conteúdo abaixo foi testado contra a API de produção; as estruturas de requisição e as mensagens de erro são reproduzidas literalmente.
O que você vai construir
Uma função: recebe um prompt de texto e devolve uma URL MP4 para download. O modelo é happyhorse-1.1/text-to-video, disponibilizado através do endpoint unificado /v1/tasks. Você submete uma tarefa, recebe imediatamente um taskId e depois faz polling até concluir ou deixa o hiapi enviar o resultado final por POST para o seu webhook.
Pré-requisitos:
- Uma API key do hiapi. Contas novas recebem 1 dólar de crédito gratuito — obtenha na página de API Keys.
- Uma forma de fazer requisições HTTPS. Não é necessário instalar SDK.
Não coloque a key no código do cliente nem a submeta ao git. Leia de uma variável de ambiente:
export HIAPI_API_KEY="sk-..."
Requisição mínima funcional
O único campo de entrada obrigatório é prompt. Todos os outros têm valores predefinidos.
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"
}
}'
Resposta:
{
"code": 200,
"data": { "taskId": "tk-hiapi-01KW23VDG2EZ20J7RQH8S67MNC" },
"message": "success"
}
Este taskId é a sua credencial para operar essa tarefa. Guarde-o com cuidado.
Python (apenas biblioteca padrão)
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
Tabela completa de parâmetros
A estrutura de parâmetros é pequena e estrita — campos desconhecidos são rejeitados com 400, não descartados silenciosamente. Seguem os campos dentro de input que a API aceita na prática, extraídos diretamente das mensagens de validação:
| Campo | Tipo | Obrigatório | Valores permitidos | Comportamento padrão |
|---|---|---|---|---|
prompt |
string | Sim | Qualquer texto não vazio descrevendo a cena | — |
aspect_ratio |
string | Não | 16:9, 9:16, 3:4, 4:3, 4:5, 5:4, 1:1, 9:21, 21:9 |
Padrão do modelo |
resolution |
string | Não | 720p, 1080p |
Padrão do modelo |
duration |
integer | Não | 3 a 15 (segundos) | Padrão do modelo |
Campos que parecem naturais, mas que atualmente não são aceitos: size, audio, seed, negative_prompt. Enviar qualquer um deles retorna additional properties '<field>' not allowed. Se você já usou outros modelos hiapi — qwen-image-2.0 usa size, z-image usa aspect_ratio — observe que happyhorse-1.1/text-to-video pertence à família da proporção de aspeto, não à família do tamanho explícito.
Uma requisição que usa todos os parâmetros suportados:
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
}
}'
Sobre custo e duração
O modelo cobra pelos segundos de vídeo gerado — consulte a página de preços para a tarifa atual. duration é a alavanca de custo; resolution não é. Passar de 5 para 15 segundos triplica o custo; no momento da redação, passar de 720p para 1080p não. Durante o desenvolvimento, use duration: 3 e uma resolução mais baixa por padrão; só suba para 1080p com prompts finalizados.
Obter o resultado: polling
A forma mais simples de buscar a saída é fazer GET da tarefa por id até que status atinja um estado terminal.
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)
Alguns detalhes importantes:
statuspassa porqueued → handling → archiving → success(oufail). Qualquer estado anterior asuccessoufailnão deve ser considerado terminal.- O tempo de geração aumenta com
duratione com a profundidade atual da fila. Tarefas de 5 segundos a 720p normalmente terminam em poucos minutos; 1080p a 15 segundos exige reservar mais tempo. output[].urlfica em armazenamento temporário. Baixe os bytes imediatamente e re-hospede por conta própria; não guarde apenas a URL para entregar ao utilizador final — ela expira.
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")
Obter o resultado: callback (modo de produção)
O polling é adequado para scripts e notebooks, mas num backend real você não vai querer que um worker fique ocupado durante minutos por vídeo. Passe um objeto callback na submissão e o hiapi fará POST do corpo da tarefa concluída exatamente uma vez para a sua 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"
}
}'
Atualmente apenas when: "final" é suportado — enviar outro valor retorna:
{ "code": 400, "error_code": "INVALID_REQUEST",
"message": "invalid callback.when: only 'final' is supported" }
Projete um recetor robusto
Três regras garantem a segurança do caminho de callback:
- Idempotência. Persista por
taskId. Se receber o mesmo callback duas vezes — o que acabará por acontecer, devido a novas tentativas — o processamento repetido deve ser uma operação sem efeito. - Desacople a confirmação do processamento. Assim que colocar o payload na fila, devolva
200 OKa partir do webhook. O download / reenvio / gravação no banco de dados real fica numa tarefa em segundo plano. Webhooks lentos atraem timeouts e entregas duplicadas. - Recorra ao polling. Guarde cada
taskIdque submeter, junto comcreated_at, na sua própria base de dados. Execute um varredor que faça polling de/v1/tasks/{id}para qualquer linha com mais de cerca de 30 minutos que ainda não tenha estado terminal. O callback é o caminho rápido, não o único caminho.
Um recetor FastAPI mínimo:
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}
Erros que você realmente vai encontrar
A forma do envelope de erro é sempre consistente — {code, error_code, message, data} — portanto você pode escrever um único handler. Alguns que merecem tratamento especial para este modelo:
| HTTP | error_code |
Condição de disparo | Como corrigir |
|---|---|---|---|
| 400 | INVALID_REQUEST |
Campo desconhecido, valor de enumeração incorreto, duration fora de 3–15 |
Verifique o message, que indicará o campo — por exemplo aspect_ratio: value must be one of '16:9'... |
| 401 | — | Key ausente ou incorreta, ou permission_denied |
Verifique novamente o cabeçalho Authorization: Bearer sk-... |
| 402 | — | Saldo insuficiente para cobrir o custo estimado | Recarregue em hiapi.ai/en/pricing e tente novamente |
| 429 | — | Você está martelando o endpoint | Recue e tente novamente com jitter |
Uma falha de validação real é mostrada abaixo — pode copiá-la como fixture de teste:
{
"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"
}
Note que cada campo é reportado na mesma resposta. Valide localmente quando puder, mas confie nisto quando não puder.
Conteúdo relacionado no hiapi
- Página do modelo happyhorse-1.1/text-to-video — playground, interface completa de parâmetros e preços em tempo real
- happyhorse-1.1/image-to-video — mesma família de modelos, mas para animar imagens estáticas
- veo-3.1-fast/text-to-video e seedance-2.0 — modelos alternativos de texto para vídeo na mesma API
/v1/tasks - Referência da API unificada de tarefas — fonte autoritativa para
POST /v1/tasks,GET /v1/tasks/:id, callbacks e envelopes de erro - Navegue por todos os modelos e preços
Perguntas frequentes
O happyhorse-1.1/text-to-video gera áudio?
Através da API de tarefas, não — a estrutura de requisição atualmente não tem campo audio, e enviá-lo retorna additional properties 'audio' not allowed. A saída é um MP4 sem som. Adicione a trilha sonora a jusante.
Quais proporções de aspeto estão disponíveis para formato vertical / vídeos curtos?
Para saídas no estilo TikTok e Reels, 9:16 é a escolha padrão. A API também aceita 4:5 (vertical para o feed do Instagram) e 9:21 (vertical extra longo), caso precise de um enquadramento mais estreito.
Qual é a duração mínima e máxima?
duration é um inteiro de 3 a 15 segundos (inclusive). Enviar 0 resulta em duration: minimum: got 0, want 3; enviar 99 resulta em duration: maximum: got 99, want 15. Escolha o menor valor que capture o movimento necessário — a duração é a parte pela qual você paga.
É possível passar seed para obter saídas reprodutíveis?
Atualmente o modelo não suporta isso. Pedir {"input": {"prompt": "...", "seed": 42}} falha com additional properties 'seed' not allowed. Se a reprodutibilidade for importante, registe o corpo completo da requisição e tente novamente com o mesmo prompt; obterá resultados semelhantes — mas não idênticos pixel a pixel.
Suporta negative_prompt?
Não. Guie a saída adicionando restrições no próprio prompt (por exemplo "...clean composition, no text, no watermarks").
Quanto tempo dura a URL de saída?
No plano padrão, o campo storage da tarefa é temp e output[].url é um link assinado de curta duração. Baixe o ficheiro e re-hospede assim que a tarefa atingir success. Não guarde a URL original para servir mais tarde.
Como controlar os gastos durante o desenvolvimento?
Dois hábitos que poupam dinheiro: mantenha duration: 3 até finalizar o prompt e submeta com resolution: "720p" até confirmar o storyboard. Só a renderização final merece 1080p e durações maiores.
Polling ou callback — qual usar?
Use ambos, nesta ordem. Primeiro o polling, porque tem menos peças móveis. Quando tiver o primeiro backend que não pode dar-se ao luxo de bloquear um worker em wait_for_task, mude para callback. Em qualquer caso, mantenha o polling como varredor de recurso; o callback é melhor esforço, não entrega garantida.
Por que recebo 402 mesmo tendo crédito para chamadas? A API estima o teto de custo da tarefa — duração × preço por segundo — e reserva antecipadamente. Se o saldo não cobrir essa estimativa, retorna 402 imediatamente. Recarregue, tente novamente e, após o sucesso, apenas o custo real é debitado.
Artigos relacionados
Ver todos os artigos →-
Tutorial
Como chamar hailuo-2.3-fast/image-to-video pela API HiAPI: curl e Python na prática
A primeira requisição executável do nível rápido e econômico de geração de vídeo a partir de imagem — do ID do modelo ao download do MP4
-
Tutorial
Como chamar hailuo-2.3/text-to-video pela API HiAPI: curl, Python e exemplos de requisição executáveis
-
Tutorial
Usar Kandinsky 6.0 Pro text-to-video na HiAPI: tutorial de curl e Python
Demonstração prática com foco na requisição: montar a chamada de prompt para clipe de vídeo, enviar a requisição, consultar o status de conclusão e baixar o resultado