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
O que hailuo-2.3-fast/image-to-video pode fazer por você
hailuo-2.3-fast/image-to-video é o nível otimizado para velocidade na linha de produtos de geração de vídeo a partir de imagem da HiAPI. Você fornece uma imagem estática mais um prompt de texto descrevendo o movimento desejado, e ele retorna um pequeno clipe de vídeo que se move a partir dessa imagem. O "fast" no ID do modelo é o ponto central: este nível troca parte da fidelidade refinada de modelos de vídeo mais pesados por menor latência e menor custo por renderização, o que o torna ideal para iteração e produção em lote.
Escolha este modelo quando precisar de uma primeira versão viável de um clipe, e não de uma peça final de "key visual": prévias de storyboard, vídeos em loop para redes sociais, material de movimento de fundo ou a fase de rascunho em um pipeline onde o modelo pesado faz a renderização final. Se a sua saída exigir planos longos, movimentos de câmera complexos ou consistência estrita de identidade entre muitos quadros, deixe essas tarefas para os níveis mais pesados — mantendo a mesma estrutura de requisição, para que a troca seja apenas uma mudança de configuração.
Tudo abaixo gira em torno de uma requisição que você realmente consegue executar. O objetivo é fazer um MP4 chegar ao disco, e não tirar print de uma resposta bem-sucedida.
Visão geral da requisição
Uma renderização de vídeo leva de segundos a minutos, então a API é assíncrona: você envia uma tarefa, recebe um identificador de tarefa e depois consulta essa tarefa até que ela informe conclusão e entregue a saída. Isso significa que toda integração tem duas fases — envio e consulta — e apenas a fase de envio varia conforme o modelo.
A requisição de envio contém quatro elementos sob seu controle:
- Endpoint e método — um
POSTpara o endpoint de geração de vídeo da HiAPI. - Cabeçalho de autenticação — sua chave, no formato
Authorization: Bearer $HIAPI_API_KEY. Coloque a chave em uma variável de ambiente e nunca a inclua diretamente no código enviado. - ID do modelo — a string literal
hailuo-2.3-fast/image-to-video. É ela que seleciona o nível. - Entrada — um
promptdescrevendo o movimento e uma imagem de primeiro frame. A imagem é fornecida como URL publicamente acessível ou data URI, dependendo do que sua conta aceita nesse endpoint; começar com URL é o mais simples. O corpo JSON é enviado comContent-Type: application/json.
Coloque esses quatro campos em um único lugar no seu código — um dicionário, um arquivo de configuração, um builder alimentado por variáveis de ambiente — para que migrar depois para outro modelo de vídeo da HiAPI seja uma mudança de uma linha, e não uma reescrita. A resposta é um pequeno envelope JSON, no qual o identificador da tarefa é o único campo que você precisa persistir; o resto é metadado de status.
Requisição curl funcional
Este é o passo de envio completo. Substitua a URL da imagem por um endereço que sua conta possa acessar e exporte sua chave primeiro.
export HIAPI_API_KEY="your-key-here"
curl -sS -X POST "https://api.hiapi.ai/v1/video/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "hailuo-2.3-fast/image-to-video",
"prompt": "slow push in, gentle breeze moving the fabric, film grain",
"image": "https://example.com/first-frame.jpg"
}'
Uma resposta bem-sucedida é um JSON contendo o identificador da tarefa, normalmente algo como {"id": "task_...", "status": "queued"}. A forma exata dos campos do envelope pode mudar, então faça o parsing de forma defensiva: leia o ID da tarefa por chave, não por posição, e trate campos desconhecidos como informativos.
Se estiver depurando, adicione -i para ver a linha de status HTTP. 401 quase sempre significa que o cabeçalho de autenticação está ausente ou que a variável expandiu para uma string vazia — execute echo $HIAPI_API_KEY antes de culpar o corpo da requisição. 400 normalmente aponta para o corpo: URL de imagem inacessível, model ausente ou nome de campo incompatível com o endpoint.
Copie o ID da tarefa retornado para uma variável de shell, para não precisar redigitá-lo nos passos seguintes:
TASK_ID=$(curl -sS -X POST "https://api.hiapi.ai/v1/video/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"hailuo-2.3-fast/image-to-video","prompt":"slow push in","image":"https://example.com/first-frame.jpg"}' \
| python -c 'import sys,json; print(json.load(sys.stdin)["id"])')
echo "$TASK_ID"
A mesma requisição, em Python
A versão em Python mantém o corpo da requisição byte a byte equivalente ao curl, então, quando você estiver investigando um bug, o resultado do curl e o do Python podem ser comparados diretamente.
import os
import requests
API_BASE = "https://api.hiapi.ai/v1"
MODEL = "hailuo-2.3-fast/image-to-video"
def submit_video(prompt: str, image_url: str) -> str:
resp = requests.post(
f"{API_BASE}/video/generations",
headers={"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"},
json={
"model": MODEL,
"prompt": prompt,
"image": image_url,
},
timeout=60,
)
resp.raise_for_status()
return resp.json()["id"]
Há três hábitos importantes aqui. Quando a variável está ausente, os.environ[...] (em vez de .get) falha de forma ruidosa, que é exatamente a forma de falha desejada. Um timeout explícito impede que um socket travado ocupe o worker indefinidamente. E raise_for_status() transforma respostas 4xx em exceções registráveis, em vez de deixar a linha seguinte lançar silenciosamente um KeyError por falta do ID.
A função deliberadamente retorna apenas o ID da tarefa. Envio e espera são preocupações separadas; misturá-las torna o loop de polling difícil de testar. Observe que o ID do modelo aparece uma única vez, no nível do módulo — é o único lugar que você precisa alterar ao migrar para outro modelo de vídeo da HiAPI.
Consultar a tarefa até o vídeo ficar pronto
A verificação de status é uma requisição GET ao recurso da tarefa. Tarefas em fila ou em execução retornam status não terminais; tarefas concluídas retornam um status terminal com a saída anexada.
import time
def wait_for_video(task_id: str, timeout: float = 600.0) -> dict:
deadline = time.monotonic() + timeout
delay = 2.0
url = f"{API_BASE}/video/generations/{task_id}"
while True:
r = requests.get(
url,
headers={"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"},
timeout=30,
)
r.raise_for_status()
task = r.json()
if task.get("status") == "succeeded":
return task
if task.get("status") in {"failed", "cancelled"}:
raise RuntimeError(f"task {task_id} ended: {task}")
if time.monotonic() >= deadline:
raise TimeoutError(f"task {task_id} still {task.get('status')} after {timeout}s")
time.sleep(delay)
delay = min(delay * 1.5, 15.0)
Este é um loop de retry, não um sleep fixo. O backoff começa em dois segundos e cresce até um teto de quinze segundos, de modo que renderizações curtas terminam rápido e renderizações longas não martelam o endpoint. O prazo é verificado antes do sleep, então o tempo total de relógio permanece dentro do limite, sem estourar um intervalo. Use time.monotonic() em vez de time.time(), para que ajustes de relógio não prolonguem o loop.
Trate failed e cancelled de forma diferente de "continuar esperando": são terminais, mas não bem-sucedidos. Quando isso ocorrer, registre o objeto de tarefa inteiro — é o único diagnóstico que você terá, e reproduzir uma falha apenas com um ID é muito penoso. Trate strings de status desconhecidas como não terminais e continue o polling, para que novos estados intermediários não derrubem seu worker.
Baixar e processar a saída
Uma tarefa concluída carrega uma ou mais URLs de saída. Obtenha os bytes e grave-os em um local sob seu controle.
def download_video(task: dict, dest: str) -> str:
url = task["output"][0]["url"] if isinstance(task.get("output"), list) else task["output"]["url"]
with requests.get(url, stream=True, timeout=120) as r:
r.raise_for_status()
with open(dest, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 20):
f.write(chunk)
return dest
Baixe por streaming, em vez de chamar .content; um vídeo pode ser grande o bastante para que armazenar tudo em memória seja desperdício em um worker que processa várias tarefas. A forma exata da URL de saída pode variar um pouco conforme o nível do modelo, então verifique antes se é uma lista antes de indexar — o ramo acima cobre os dois casos.
Regra importante: trate a URL retornada como um handle de curta duração, não como armazenamento. Assim que a tarefa for concluída, copie o arquivo para o seu próprio bucket ou caminho local e sirva a partir dali. Persistir a URL em um banco de dados e buscá-la dias depois é o motivo pelo qual você recebe 403 e vê um painel vazio. Para armazenamento de objetos, o mesmo loop de bytes por streaming pode alimentar diretamente o método de upload do seu cliente — se o seu SDK aceitar um objeto semelhante a arquivo, não será necessário um arquivo temporário intermediário.
Dicas práticas e armadilhas comuns
- Use o cabeçalho de autenticação, não parâmetros de consulta. A chave vai em
Authorization: Bearer .... Uma chave colada em uma URL é uma chave nos logs de acesso. - Acessibilidade da imagem importa mais que a correção da imagem. O
400no envio é muito mais frequentemente uma URL que a API não consegue buscar — localhost, bucket privado, host com proteção contra hotlink, link que expira — do que um problema no prompt. Antes de depurar qualquer outra coisa, teste a URL em uma sessão de navegador nova. - Content-Type do corpo. Envie JSON com
Content-Type: application/json. Se esquecer o-Hao enviar com curl, o servidor pode simplesmente não interpretar o corpo. - Polling apertado demais. Um
while Truesem sleep vai fazer você ser limitado por taxa e não fará a renderização terminar mais rápido. Backoff mais prazo é todo o segredo. - Nenhum timeout em lugar algum. Defina timeouts nas chamadas de envio, polling e download. A falta de timeout transforma um provedor lento em uma fila travada no seu próprio sistema.
- O prompt descreve movimento, não a imagem. Descreva o que muda entre os quadros — movimento de câmera, ação do sujeito, elementos do ambiente — e mantenha curto. Reafirmar a imagem estática não acrescenta nada.
- Uma configuração, vários modelos. Mantenha o ID do modelo, o prompt e a imagem de entrada no mesmo builder de payload. Migrar deste nível rápido para um nível mais pesado passa a exigir a mudança de uma única string.
A forma mais rápida de validar a configuração de ponta a ponta é: execute o envio por curl, cole o ID da tarefa no loop de polling e confirme que um MP4 chega ao disco — uma vez que isso funciona, o resto é trabalho de empacotamento.
Artigos relacionados
Ver todos os artigos →-
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
-
Tutorial
Como usar kling-4.0/text-to-video através da API HiAPI: curl, Python e um exemplo de requisição executável
Guia prático para desenvolvedores: prompts cinematográficos, áudio sincronizado, polling assíncrono e download da saída