HiAPI
Blogue

Tutorial

Como usar a API hailuo 2.3 image-to-video: curl, Python e um exemplo de requisição executável

Jul 11, 2026
Como usar a API hailuo 2.3 image-to-video: curl, Python e um exemplo de requisição executável

Resumo

  • O que é isto: uma receita executável para usar a API hailuo-2.3/image-to-video na hiapi — transformar uma imagem estática em um clipe curto de vídeo: criar a tarefa, fazer polling da tarefa, baixar o .mp4.
  • Endpoint: POST https://api.hiapi.ai/v1/tasks, com "model": "hailuo-2.3/image-to-video" no corpo da requisição, depois faça polling com GET /v1/tasks/<taskId> até a tarefa atingir um estado terminal.
  • Dois campos de entrada obrigatórios: prompt e image_url — uma única URL pública apontando para o seu primeiro frame (não um array).
  • duration é uma string, não um número: "6" ou "10". Enviar o inteiro 6 é rejeitado com 400.
  • O schema é estrito. Não existem os parâmetros motion_strength, camera_movement ou resolution — enviá-los é rejeitado com 400. O movimento é controlado inteiramente pelo prompt.

O que você vai construir e o que precisa ter pronto

Você tem uma imagem estática — foto de produto, render de personagem, fotografia — e quer que o hailuo-2.3 a anime, gerando um clipe de vídeo que começa exatamente neste frame. O hailuo-2.3 é conhecido por movimentos fisicamente convincentes (tecidos, cabelo, mecânica corporal), o que o torna ideal para dar vida a imagens estáticas.

Você precisa de duas coisas:

  1. Uma API key da hiapi. Crie uma no dashboard. A key tem o formato sk-... e vai no cabeçalho Authorization: Bearer.
  2. Uma URL de imagem do primeiro frame acessível publicamente. A API busca a imagem a partir de image_url, portanto ela precisa estar acessível sem autenticação — links de S3/R2/CDN funcionam; caminhos localhost ou URLs assinadas expiradas não funcionam.

Como todo modelo de geração na hiapi, este modelo usa a interface de tarefas assíncrona: você faz um POST de uma tarefa, recebe imediatamente um taskId e depois obtém o resultado por polling ou callback. Não há endpoint síncrono para vídeo — a renderização do clipe leva tempo e ultrapassaria o timeout típico de HTTP.

Schema da requisição

O schema de entrada completo de hailuo-2.3/image-to-video — verificado contra a API em produção:

Campo Tipo Obrigatório Descrição
prompt string ✅ Descreva o movimento, não a cena — a cena vem da sua imagem.
image_url string ✅ Uma URL pública, ou seja, o primeiro frame. É singular — não image_urls.
duration string Opcional "6" ou "10" (segundos). Precisa ser string.
prompt_optimizer boolean Opcional Permite que a plataforma expanda seu prompt antes da geração.

O schema é estrito: qualquer coisa que não esteja nesta tabela é rejeitada. Envie motion_strength, camera_movement ou resolution e você receberá exatamente isto:

{
  "code": 400,
  "error_code": "INVALID_REQUEST",
  "message": "invalid input: <root>: additional properties 'resolution', 'camera_movement', 'motion_strength' not allowed"
}

Duas consequências valem ser lembradas:

  • Não há botão de ajuste de movimento. A física de movimento do hailuo-2.3 é guiada apenas pelo prompt — veja a seção de escrita de prompts abaixo.
  • Não há controle de resolução ou proporção de aspecto neste endpoint. Não construa UI para parâmetros que a API vai rejeitar com 400.

Exemplo mínimo executável: curl

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": "hailuo-2.3/image-to-video",
    "input": {
      "prompt": "The woman turns toward the camera and smiles; wind gently moves her hair; slow cinematic push-in",
      "image_url": "https://your-cdn.example.com/first-frame.jpg",
      "duration": "6",
      "prompt_optimizer": true
    }
  }'

A resposta (trecho) contém o id da tarefa em data.taskId:

{ "data": { "taskId": "..." } }

Faça polling:

curl -s https://api.hiapi.ai/v1/tasks/<taskId> \
  -H "Authorization: Bearer sk-<your-key>"

Durante a renderização do clipe, data.status reporta um estado não terminal. Quando ele mudar para "success", a URL do vídeo estará em data.output[0].url; se for "fail", data.error.code e data.error.message explicarão o motivo.

Baixe o arquivo imediatamente após o sucesso da tarefa. A URL de saída é assinada e tem data de expiração (expireAt) — armazene os bytes no seu próprio storage e nunca faça hotlink da URL da tarefa.

Exemplo mínimo executável: Python

Script completo com o mesmo fluxo — criar, fazer polling, baixar:

import time
import requests

API_BASE = "https://api.hiapi.ai/v1/tasks"
API_KEY = "sk-<your-key>"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}


def create_task() -> str:
    payload = {
        "model": "hailuo-2.3/image-to-video",
        "input": {
            "prompt": (
                "The cat stretches, hops off the windowsill, and walks toward "
                "the camera; soft afternoon light, subtle handheld camera sway"
            ),
            "image_url": "https://your-cdn.example.com/first-frame.jpg",
            "duration": "6",          # string, not int: "6" or "10"
            "prompt_optimizer": True,
        },
    }
    r = requests.post(API_BASE, json=payload, headers=HEADERS, timeout=30)
    task_id = (r.json().get("data") or {}).get("taskId")
    if not task_id:
        raise RuntimeError(f"create failed ({r.status_code}): {r.text[:300]}")
    return task_id


def wait_task(task_id: str, timeout_s: int = 900, poll_s: int = 10) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(f"{API_BASE}/{task_id}", headers=HEADERS, timeout=30)
        task = r.json().get("data") or {}
        status = task.get("status")
        if status == "success":
            return task
        if status == "fail":
            err = task.get("error") or {}
            raise RuntimeError(f"task failed: {err.get('code')}: {err.get('message')}")
        time.sleep(poll_s)
    raise TimeoutError(f"task {task_id} not finished after {timeout_s}s")


def main() -> None:
    task_id = create_task()
    print("task created:", task_id)
    task = wait_task(task_id)
    url = task["output"][0]["url"]
    # The URL is signed and expires -- download immediately, store the bytes yourself.
    video = requests.get(url, timeout=120).content
    with open("hailuo-output.mp4", "wb") as f:
        f.write(video)
    print("saved hailuo-output.mp4")


if __name__ == "__main__":
    main()

Tarefas de vídeo demoram bem mais que tarefas de imagem, então o script faz polling a cada 10 segundos e define um limite folgado de 15 minutos. Não faça polling com mais agressividade que a cada ~5 segundos — isso não deixa a renderização mais rápida.

Escrevendo prompts que controlam o movimento

Como não há nenhum parâmetro de movimento, o prompt é o seu único volante — e, para o hailuo-2.3, isso é suficiente. Três regras práticas:

  • Descreva o movimento, não a aparência. O modelo já viu sua imagem. "a beautiful woman in a red dress" desperdiça o prompt; "she turns her head slowly to the left as the camera pushes in" faz bom uso dele.
  • Descreva o movimento de câmera em linguagem simples. "slow push-in", "orbit right", "handheld sway", "static camera" — isso deve estar no texto do prompt, não em um campo (inexistente) camera_movement.
  • Na dúvida, deixe o prompt_optimizer ajudar. Com "prompt_optimizer": true, a plataforma expande prompts curtos antes da geração — um bom padrão para entradas curtas. Desligue-o quando quiser que uma formulação cuidadosamente elaborada seja seguida à risca.

Considerações para produção

Prefira callbacks ao polling. Anexe um callback ao criar a tarefa e a hiapi fará um POST do resultado terminal para o seu endpoint, assim você não precisa manter um loop aberto:

{
  "model": "hailuo-2.3/image-to-video",
  "input": { "...": "..." },
  "callback": { "url": "https://your-app.example.com/hooks/hiapi", "when": "final" }
}

"when": "final" dispara uma única vez no estado terminal. Polling serve para scripts e notebooks; callbacks são a forma correta para servidores — a renderização de vídeo demora o suficiente para que um worker bloqueado em polling seja um custo real.

Trate explicitamente estes erros:

  • 401 com "code": "permission_denied" — há um problema com a key, ou essa key não tem permissão para usar este modelo. A mensagem contém um request_id que pode ser enviado ao suporte.
  • 400 INVALID_REQUEST — violação de schema. A mensagem é precisa, por exemplo duration: got number, want string ou image_url: missing required field "image_url". Registre-a exatamente como veio.
  • 402 ao criar a tarefa — saldo insuficiente. Note que vídeo custa mais por tarefa que imagem, então uma key que ainda executa tarefas de imagem normalmente pode começar a falhar ao criar vídeos. Recarregue ou verifique o uso no dashboard.
  • A tarefa entra em "fail" — falha no lado da geração; verifique data.error. Uma causa comum específica de image-to-video: não foi possível buscar a image_url (bucket privado, URL assinada expirada).

Seja idempotente em relação a retentativas. Se seu processo morrer entre a criação e o download, uma reinicialização ingênua criará (e cobrará) uma segunda renderização. Persista o taskId imediatamente após o retorno de create_task e, ao reiniciar, continue o polling em vez de criar de novo.

Leituras relacionadas

Perguntas frequentes

Quais durações o hailuo-2.3 image-to-video suporta? Duas: "6" e "10" segundos, passadas como string. Qualquer outro valor — ou o mesmo valor escrito como número — retorna 400 INVALID_REQUEST.

Por que "duration": 6 falha, mas "duration": "6" funciona? O schema define duration como um enum de strings. A API informa claramente: duration: got number, want string. Este é o 400 mais comum neste endpoint.

Posso definir a resolução ou a proporção de aspecto da saída? Não. O endpoint não expõe os campos resolution ou aspect_ratio, e enviar um deles resulta em 400 — o schema rejeita propriedades desconhecidas em vez de ignorá-las.

Como controlar o movimento de câmera ou a intensidade do movimento? Pelo prompt. Não há parâmetros camera_movement ou motion_strength — escreva instruções de câmera e movimento em linguagem simples ("slow push-in", "she walks toward the camera").

Posso enviar várias imagens de referência? Não — image_url é uma única string, ou seja, o primeiro frame. Isso difere de alguns outros modelos na hiapi que recebem um array image_urls; o schema é definido por modelo, então sempre consulte a página do modelo.

Quais formatos de imagem o primeiro frame suporta? Formatos Web padrão (JPEG/PNG), servidos a partir de uma URL publicamente acessível. Se a plataforma não conseguir buscar a URL, a tarefa falhará após a criação — então valide que o link é publicamente acessível antes de enviar.

Qual é a diferença em relação ao hailuo-2.3/text-to-video? O text-to-video inventa a cena apenas a partir do seu prompt; o image-to-video fixa o primeiro frame na sua imagem e anima a partir dele. O ciclo de vida da tarefa é idêntico — apenas o schema de entrada difere (aqui image_url é obrigatório, enquanto o text-to-video não tem campo de imagem).

Quanto custa? Vídeos são cobrados por segundo de saída, e as taxas mudam — consulte a página do modelo hailuo-2.3/image-to-video e a página de preços para os números atuais.

Artigos relacionados

Ver todos os artigos →

Comece a criar com artefactos de IA persistentes

Obtenha a sua chave de API e chame modelos de imagem, vídeo e áudio através de uma única API baseada em tasks.

Ler a documentação da API