HiAPI
Blogue

Tutorial

Como usar a API de imagem para vídeo hailuo 2.3 Fast: curl, Python e requisições executáveis

Jul 10, 2026
Como usar a API de imagem para vídeo hailuo 2.3 Fast: curl, Python e requisições executáveis

Você tem uma imagem estática e quer transformá-la em um vídeo curto. Este guia integra o modelo hailuo-2.3-fast/image-to-video através da API de tarefas do hiapi: o schema de entrada exato (já verificado contra o validador online), uma requisição curl pronta para copiar, um script Python completo que faz polling até a tarefa concluir, e detalhes de produção — callbacks, retentativas idempotentes e as respostas de erro que você realmente vai encontrar.

O que você precisa preparar

  • Uma chave de API hiapi — obtenha no seu console. A chave tem o formato sk-... e vai no cabeçalho Authorization: Bearer.
  • Uma imagem de origem hospedada em uma URL HTTPS acessível publicamente. A API recebe uma URL, não um arquivo enviado.
  • Qualquer cliente HTTP. Os exemplos abaixo usam curl e requests do Python.

O preço por segundo deste modelo está listado na página de preços.

Schema de entrada (verificado)

hailuo-2.3-fast/image-to-video usa o endpoint unificado de tarefas assíncronas: POST /v1/tasks cria a tarefa, GET /v1/tasks/<taskId> consulta a tarefa. O schema de entrada é rigoroso — campos desconhecidos são rejeitados com 400, em vez de serem silenciosamente ignorados.

Campo Tipo Obrigatório Descrição
prompt string ✅ Descreve o movimento desejado
image_url string ✅ Forma singular — uma string de URL, não um array image_urls
duration string Opcional "6" ou "10" — é uma string, não um número
prompt_optimizer boolean Opcional Deixa o backend reescrever seu prompt para o movimento

O validador pega três armadilhas comuns:

  1. duration deve ser uma string. Enviar "duration": 6 retorna 400 com a mensagem duration: got number, want string. Envie "duration": "6".
  2. O campo é o singular image_url. Muitos outros modelos de vídeo da plataforma (Grok Imagine, Kling) recebem um array image_urls. Este modelo recebe uma única string.
  3. Não há resolution, aspect_ratio, seed, watermark nem end_image_url. Todos eles retornam additional properties ... not allowed. O formato de saída segue sua imagem de origem; o controle de movimento só é possível via prompt.

Requisição mínima executável: curl

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "hailuo-2.3-fast/image-to-video",
    "input": {
      "prompt": "A câmera avança lentamente, vapor subindo da xícara de café, luz suave da manhã",
      "image_url": "https://your-cdn.example.com/coffee.jpg",
      "duration": "6"
    }
  }'

Resposta de sucesso:

{
  "code": 200,
  "data": { "taskId": "tk-hiapi-01KX4W3SVW8S8EHD5VX45JH95V" },
  "message": "success"
}

Depois, faça polling:

curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-01KX4W3SVW8S8EHD5VX45JH95V \
  -H "Authorization: Bearer sk-YOUR_KEY"

Enquanto o vídeo renderiza, data.status é "handling":

{
  "code": 200,
  "data": {
    "taskId": "tk-hiapi-01KX4W3SVW8S8EHD5VX45JH95V",
    "model": "hailuo-2.3-fast/image-to-video",
    "status": "handling",
    "storage": "temp",
    "completed": 0,
    "created": 1783648872
  },
  "message": "success"
}

Os estados terminais são "success" (a URL do vídeo fica em data.output[0].url) e "fail" (detalhes em data.error).

Script Python completo

import os
import time

import requests

API_BASE = "https://api.hiapi.ai/v1/tasks"
TOKEN = os.environ["HIAPI_API_KEY"]  # sk-...
HEADERS = {"Authorization": f"Bearer {TOKEN}"}


def create_task(prompt: str, image_url: str, duration: str = "6") -> str:
    resp = requests.post(
        API_BASE,
        headers={**HEADERS, "Content-Type": "application/json"},
        json={
            "model": "hailuo-2.3-fast/image-to-video",
            "input": {
                "prompt": prompt,
                "image_url": image_url,
                "duration": duration,  # string: "6" ou "10"
            },
        },
        timeout=60,
    )
    body = resp.json()
    if resp.status_code != 200 or not body.get("data", {}).get("taskId"):
        raise RuntimeError(f"create failed: {resp.status_code} {body}")
    return body["data"]["taskId"]


def wait_task(task_id: str, timeout_s: int = 600, poll_s: int = 5) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        task = requests.get(f"{API_BASE}/{task_id}", headers=HEADERS, timeout=30).json()["data"]
        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} still running after {timeout_s}s")


if __name__ == "__main__":
    task_id = create_task(
        prompt="O gato pisca os olhos, vira a cabeça para a janela, uma brisa suave passa pelo seu pelo",
        image_url="https://your-cdn.example.com/cat.jpg",
        duration="6",
    )
    print("task:", task_id)

    task = wait_task(task_id)
    video_url = task["output"][0]["url"]
    print("video:", video_url)

    # A URL fica em armazenamento temporário com validade — baixe imediatamente, não a use como link direto.
    mp4 = requests.get(video_url, timeout=120).content
    with open("output.mp4", "wb") as f:
        f.write(mp4)
    print(f"saved output.mp4 ({len(mp4)} bytes)")

Este script acerta duas coisas que soluções montadas às pressas costumam ignorar:

  • Ele baixa o vídeo imediatamente. storage: "temp" significa que a URL de saída tem validade (expireAt). Persista os bytes no seu próprio armazenamento; nunca guarde a URL retornada no seu banco de dados.
  • Ele trata fail como estado terminal. Consultar novamente uma tarefa com falha não a revive — crie uma nova tarefa.

Considerações para produção

Use callbacks em vez de polling

Para casos de uso além de scripts, registre um webhook ao criar a tarefa e pule completamente o loop de polling:

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

Com "when": "final", seu endpoint é chamado uma vez quando a tarefa chega a success ou fail. Regra geral: polling é adequado para CLI, tarefas em lote e qualquer cenário efêmero; assim que você tem um servidor já escutando, callbacks são melhores — tarefas de vídeo levam vários minutos, e um loop de polling durante esses minutos é uma conexão ocupada e um worker desperdiçado.

Idempotência

A chamada de criação em si não é idempotente — cada POST /v1/tasks que passa na validação cria uma nova tarefa (cobrada). Se seu executor de tarefas tenta novamente em caso de timeout, armazene o taskId obtido usando como chave o seu próprio id de requisição antes de processar o resultado. Em uma retentativa, se essa chave já tiver um taskId, consulte-o em vez de criar uma tarefa duplicada.

Erros que você realmente vai encontrar

Sintoma Significado Correção
401 — {"error":{"code":"permission_denied","type":"hiapi_error"}} Chave inválida, ou a chave não pode usar este modelo Verifique a chave no console; a resposta inclui um request_id para suporte
400 INVALID_REQUEST — duration: got number, want string Usou um número JSON em vez de uma string "duration": "6"
400 INVALID_REQUEST — additional properties '...' not allowed Você enviou um campo fora do schema Remova-o — a mensagem indica cada campo inválido
400 INVALID_REQUEST — missing required field "prompt" / "image_url" Entrada vazia ou nome de campo errado Os dois campos são obrigatórios
A tarefa chega a fail após ser criada Falha em tempo de execução — causa comum é uma URL de imagem que o backend não consegue buscar O validador não busca sua imagem na criação; garanta que a URL seja acessível publicamente

A última linha merece destaque: a validação na criação cobre apenas o schema. Um image_url com erro de digitação ou privado vai te dar um taskId aparentemente normal e uma tarefa que morre alguns minutos depois — então configure alertas para fail, não apenas para erros HTTP.

Leitura relacionada

Perguntas frequentes

Posso definir resolução ou proporção? Não. O schema de hailuo-2.3-fast/image-to-video rejeita resolution e aspect_ratio como campos desconhecidos. O formato segue sua imagem de origem; se precisar de uma proporção específica, recorte a imagem de entrada primeiro.

Quais durações são suportadas? Exatamente duas: "6" e "10" segundos, passadas como string. Qualquer outro valor retorna 400 com a mensagem value must be one of '6', '10'.

Posso enviar várias imagens de referência? Não — o campo é uma única string image_url. Modelos que aceitam arrays de imagens (por exemplo, image_urls do Grok Imagine) usam um schema diferente; não copie o corpo da requisição entre modelos sem verificar.

Como controlar o movimento? Apenas pelo prompt — não há parâmetros de intensidade de movimento ou de câmera. Descreva o movimento de forma explícita ("a câmera avança lentamente", "o cabelo balança com o vento"). Opcionalmente, defina "prompt_optimizer": true para que o backend expanda seu prompt.

Existe uma variante mais lenta e de maior qualidade? Sim — hailuo-2.3/image-to-video (sem o -fast) também está disponível na plataforma, e hailuo-2.3/text-to-video cobre cenários sem imagem de origem. Verifique o schema de cada variante antes de trocar; os campos de entrada não são necessariamente iguais.

Quanto custa? A cobrança é calculada pela quantidade de segundos do vídeo gerado e varia por modelo; confira a tarifa atual na página de preços.

É preciso uma chave de API para testar? Sim — todas as chamadas da API de tarefas exigem uma chave Bearer sk-.... Você pode criar uma no console e testá-la primeiro no playground da página do modelo.

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