Tutorial
API de imagem para vídeo do Happyhorse: exemplos funcionais com curl e Python (hiapi)
Você quer transformar uma imagem estática em um clipe curto e coerente a partir do seu próprio código. O modelo happyhorse-1.1/image-to-video no hiapi faz exatamente isso: você envia uma URL de imagem e um prompt de movimento opcional e, em poucos segundos, recebe de volta um MP4 em 720p ou 1080p. Este guia é o caminho funcional mais curto — corpo de requisição real, mensagens de erro reais, com curl e Python — para você colocar em produção hoje sem ficar brigando com nomes de campos.
O que você precisa antes de começar
- Uma API key do hiapi. Obtenha em console do hiapi — ela se parece com
sk-...e vai no cabeçalhoAuthorization: Bearer .... - Uma URL de imagem acessível (HTTPS público). Arquivo local não funciona; o modelo busca a URL por conta própria.
- Uma intenção de movimento expressa em texto (opcional, mas recomendado). Quanto mais claros os verbos, melhor o movimento.
O modelo é cobrado por segundo de vídeo, e os preços estão na página de preços do hiapi; o restante do catálogo está em hiapi models — confira os dois antes de aumentar o volume.
A requisição mínima que funciona (curl)
Todo vídeo no hiapi passa pelo endpoint assíncrono /v1/tasks: você primeiro faz um POST de criação, recebe um taskId e então ou faz polling em GET /v1/tasks/{taskId}, ou recebe um callback quando concluído.
Criar a tarefa:
curl -X POST "https://api.hiapi.ai/v1/tasks" \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "happyhorse-1.1/image-to-video",
"input": {
"image_urls": ["https://example.com/your-still.jpg"],
"prompt": "the cat slowly turns its head toward the camera and blinks",
"resolution": "1080p",
"duration": 5
}
}'
Uma resposta de sucesso se parece com isto:
{
"code": 200,
"data": { "taskId": "tk-hiapi-01KW3D..." },
"message": "success"
}
Agora faça polling do resultado (a cada 5 segundos é suficiente — a maioria dos clipes termina em menos de dois minutos):
curl "https://api.hiapi.ai/v1/tasks/tk-hiapi-01KW3D..." \
-H "Authorization: Bearer sk-your-key"
Enquanto estiver rodando, você verá "status": "handling". Quando concluir, você receberá:
{
"code": 200,
"data": {
"status": "success",
"model": "happyhorse-1.1/image-to-video",
"output": [
{
"type": "video",
"url": "https://temp.hiapi.ai/.../01KW....mp4",
"expireAt": 1783131245
}
],
"taskId": "tk-hiapi-01KW3D..."
}
}
Essa url é o que você deve baixar — e deve baixar imediatamente, porque expireAt é um timestamp Unix que marca quando o armazenamento temporário apagará o arquivo.
Python: faça polling até o sucesso
Este é um script completo e executável — basta preencher sua key e a URL da imagem.
import os
import time
import requests
API = "https://api.hiapi.ai/v1/tasks"
TOKEN = os.environ["HIAPI_KEY"] # sk-...
def create_video(image_url: str, prompt: str, duration: int = 5,
resolution: str = "1080p") -> str:
"""Submit an image-to-video task. Returns the taskId."""
r = requests.post(
API,
headers={"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json"},
json={
"model": "happyhorse-1.1/image-to-video",
"input": {
"image_urls": [image_url], # exactly one URL
"prompt": prompt, # optional
"resolution": resolution, # "720p" or "1080p"
"duration": duration, # int, 3..15
},
},
timeout=60,
)
body = r.json()
if body.get("code") != 200:
raise RuntimeError(f"create failed: {body}")
return body["data"]["taskId"]
def wait_for_video(task_id: str, timeout_s: int = 600,
poll_s: int = 5) -> str:
"""Block until the task succeeds, fails, or times out. Returns the MP4 URL."""
deadline = time.time() + timeout_s
while time.time() < deadline:
r = requests.get(
f"{API}/{task_id}",
headers={"Authorization": f"Bearer {TOKEN}"},
timeout=30,
)
task = r.json().get("data") or {}
status = task.get("status")
if status == "success":
return task["output"][0]["url"]
if status == "fail":
raise RuntimeError(f"task failed: {task.get('error')}")
time.sleep(poll_s)
raise TimeoutError(f"task {task_id} did not finish in {timeout_s}s")
def download(url: str, path: str) -> None:
with requests.get(url, stream=True, timeout=120) as r:
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(1 << 16):
f.write(chunk)
if __name__ == "__main__":
task_id = create_video(
image_url="https://example.com/your-still.jpg",
prompt="the dancer spins once and ends in a pose",
duration=5,
resolution="1080p",
)
print("submitted:", task_id)
mp4_url = wait_for_video(task_id)
download(mp4_url, "out.mp4")
print("saved out.mp4")
O conjunto todo tem cerca de 50 linhas e é toda a superfície de API pronta para produção — sem SDK, sem fila em background, sem protocolo de streaming. Execute com HIAPI_KEY=sk-... python video.py.
Produção: use callbacks em vez de polling
Polling funciona bem para tarefas pontuais e testes. Para carga de servidor, você quer que a plataforma chame você de volta quando o clipe estiver pronto — elimina o loop, escala sem trabalho extra e faz o fluxo de requisições retornar imediatamente.
Adicione um objeto callback no nível raiz do corpo da requisição (não dentro de input):
curl -X POST "https://api.hiapi.ai/v1/tasks" \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "happyhorse-1.1/image-to-video",
"input": {
"image_urls": ["https://example.com/still.jpg"],
"prompt": "slow zoom out from the subject",
"resolution": "1080p",
"duration": 6
},
"callback": {
"url": "https://your-app.example.com/webhooks/hiapi",
"when": "final"
}
}'
Quando a tarefa atinge um estado final, o hiapi faz um POST para sua URL com o objeto de tarefa que você teria obtido via polling (status, output[].url, error etc.). when: "final" significa que você só recebe callbacks de sucesso ou falha — sem ruído de progresso.
Algumas regras que economizam tempo real de depuração:
- Faça o webhook ser idempotente. O mesmo
taskIdpode chegar duas vezes (retentativas de rede são reais). Consulte esse task no banco antes de processar. - Realoque o MP4 dentro do handler. A URL que o hiapi entrega aponta para armazenamento temporário com um campo
expireAt; grave-o no seu próprio bucket logo na primeira vez que recebê-lo, ou você o perderá. - Confirme rapidamente. Retorne
2xxdepressa e faça o trabalho pesado de forma assíncrona. Webhooks lentos correm risco de retentativa. - Use polling como fallback. Enfileire uma tarefa atrasada com o
taskIde faça polling após 10 minutos, caso o webhook se perca. Se isso acontecer recorrentemente, veja motivos pelos quais callbacks de tarefa do hiapi podem não disparar para as causas comuns.
Mensagens de erro que você encontrará
Estas são as strings exatas retornadas pela API; correspondê-las no seu código é mais confiável do que adivinhar.
| Situação | HTTP | Formato do corpo da resposta |
|---|---|---|
| Key inválida ou revogada | 401 | {"error":{"code":"permission_denied","message":"This API key cannot use the selected model...","type":"hiapi_error"}} |
| Campo obrigatório ausente | 400 | {"code":400,"error_code":"INVALID_REQUEST","message":"invalid input: image_urls: missing required field \"image_urls\""} |
resolution inválido |
400 | ... resolution: value must be one of '720p', '1080p' |
| Duração fora do intervalo | 400 | ... duration: minimum: got 2, want 3 / duration: maximum: got 30, want 15 |
Comprimento de image_urls incorreto |
400 | ... image_urls: maxItems: got 2, want 1 (ou minItems: got 0, want 1) |
Campo desconhecido na raiz ou dentro de input |
400 | ... additional properties 'identity_preserving' not allowed |
| A própria tarefa falha após o envio | 200 (GET da tarefa) com data.status == "fail" e data.error.{code,message} |
Erros de autenticação e validação significam que você não deve tentar novamente; todos os outros casos (5xx da plataforma, timeout de rede durante o polling, task fail transitório com código retentável) são adequados para retentativas com pequeno backoff.
Ajustando a saída
Há quatro botões que realmente importam — qualquer outra coisa (seed, aspect_ratio, fps, cfg_scale, negative_prompt) é atualmente rejeitada como campo desconhecido. Fique dentro deste conjunto:
image_urls— uma URL HTTPS. A proporção e a composição da saída seguem esta imagem.prompt— uma descrição curta de movimento ("she smiles and tilts her head"). Prompts cinematográficos guiados por verbos vencem listas de adjetivos.resolution—"720p"para prototipagem e thumbnails,"1080p"para entrega final.duration— segundos inteiros, de3a15. Clipes mais longos custam mais tempo e dinheiro; escolha a menor duração que conte a história.
Para projetos que precisam de frame inicial e final, consulte happyhorse-1.1 reference-to-video. Para texto-para-vídeo puro sem imagem de origem, happyhorse-1.1 text-to-video é o artigo irmão. Explore o restante no catálogo de modelos do hiapi ou na seção de receitas do blog.
FAQ
Posso enviar duas imagens para controlar o primeiro e o último frame?
No happyhorse-1.1/image-to-video, não — image_urls aceita no máximo um elemento (maxItems: 1). Se precisar disso, use happyhorse-1.1/reference-to-video ou construa com happyhorse-1.1 reference-to-video.
Qual resolução tem o MP4 que eu efetivamente recebo?
A saída segue a proporção da sua imagem de entrada e a flag resolution que você definir. "1080p" entrega a maior das duas; "720p" é mais rápido e mais barato para testes.
Por que minha key retorna 401 permission_denied em outros modelos?
Alguns modelos são liberados por key. Confira a página do modelo no console do hiapi — se happyhorse-1.1/image-to-video não estiver na sua lista de permitidos, gere uma nova key ou entre em contato com o suporte informando o request_id do erro.
Existe resposta em streaming/SSE? Não. Todo modelo de vídeo usa o padrão de tarefa assíncrona deste artigo. Escolha polling ou callback conforme você controla ou não o servidor.
Onde vejo o custo real por clipe?
Na página de preços do hiapi. O custo é por segundo de vídeo, então o duration que você envia determina diretamente o gasto — mantenha clipes de desenvolvimento/teste em duration: 3 para economizar orçamento.
A URL de saída deixa de funcionar depois de um dia.
Isso é esperado — a URL aponta para armazenamento temporário e a resposta inclui um timestamp Unix expireAt. Sempre baixe o MP4 no momento em que a tarefa concluir (ou faça streaming via seu próprio CDN).
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