HiAPI
Blog

Tutorial

Cómo usar la API de imagen a vídeo de hailuo 2.3: curl, Python y un ejemplo de solicitud ejecutable

Jul 11, 2026
Cómo usar la API de imagen a vídeo de hailuo 2.3: curl, Python y un ejemplo de solicitud ejecutable

Resumen

  • Qué es esto: una receta ejecutable para usar la API hailuo-2.3/image-to-video en hiapi — convierte una imagen estática en un vídeo corto: crea la tarea, sondea la tarea, descarga el .mp4.
  • Endpoint: POST https://api.hiapi.ai/v1/tasks con "model": "hailuo-2.3/image-to-video" en el cuerpo de la solicitud, luego sondea con GET /v1/tasks/<taskId> hasta que la tarea alcance un estado terminal.
  • Dos campos de entrada obligatorios: prompt e image_url — una única URL pública que apunta a tu primer fotograma (no un array).
  • duration es una cadena, no un número: "6" o "10". Enviar el entero 6 será rechazado con 400.
  • El schema es estricto. No hay parámetros motion_strength, camera_movement ni resolution — enviarlos será rechazado con 400. El movimiento se controla completamente mediante el prompt.

Qué vas a construir y qué necesitas preparar

Tienes una imagen estática — una foto de producto, un render de personaje, una fotografía — y quieres que hailuo-2.3 la anime, generando un clip de vídeo que comience exactamente desde ese fotograma. hailuo-2.3 es conocido por su movimiento físicamente creíble (telas, cabello, mecánica corporal), lo que lo hace ideal para dar vida a imágenes estáticas.

Necesitas dos cosas:

  1. Una clave de API de hiapi. Crea una en el dashboard. La clave tiene la forma sk-... y se coloca en la cabecera Authorization: Bearer.
  2. Una URL de imagen de primer fotograma accesible públicamente. La API obtiene la imagen desde image_url, así que debe ser accesible sin autenticación — los enlaces S3/R2/CDN sirven; una ruta localhost o una URL firmada caducada no.

Como todos los modelos de generación en hiapi, este modelo usa la interfaz de tareas asíncrona: haces un POST de una tarea, recibes inmediatamente un taskId y luego recoges el resultado mediante sondeo o callback. No hay endpoint síncrono para vídeo — el renderizado del clip lleva tiempo y superaría los tiempos de espera HTTP típicos.

Schema de la solicitud

El schema de entrada completo de hailuo-2.3/image-to-video — verificado contra la API en producción:

Campo Tipo Obligatorio Descripción
prompt string ✅ Describe el movimiento, no la escena — la escena viene de tu imagen.
image_url string ✅ Una URL pública, el primer fotograma. Es singular — no image_urls.
duration string Opcional "6" o "10" (segundos). Debe ser una cadena.
prompt_optimizer boolean Opcional Permite que la plataforma expanda tu prompt antes de generar.

El schema es estricto: cualquier cosa que no esté en esta tabla será rechazada. Envía motion_strength, camera_movement o resolution y recibirás exactamente esto:

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

Hay dos consecuencias que conviene recordar:

  • No hay perillas para ajustar el movimiento. La física del movimiento de hailuo-2.3 se guía únicamente por el prompt — consulta la sección de redacción de prompts más abajo.
  • No hay control de resolución ni de relación de aspecto en este endpoint. No construyas una interfaz para parámetros que la API rechazará con 400.

Ejemplo mínimo ejecutable: curl

Crear la tarea:

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": "La mujer gira hacia la cámara y sonríe; el viento mueve suavemente su cabello; lento acercamiento cinematográfico",
      "image_url": "https://your-cdn.example.com/first-frame.jpg",
      "duration": "6",
      "prompt_optimizer": true
    }
  }'

La respuesta (extracto) contiene el id de tarea en data.taskId:

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

Sondéala:

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

Mientras el clip se renderiza, data.status informa un estado no terminal. Cuando cambie a "success", la URL del vídeo estará en data.output[0].url; si es "fail", data.error.code y data.error.message explicarán el motivo.

Descarga el archivo en cuanto la tarea tenga éxito. La URL de salida está firmada y tiene fecha de caducidad (expireAt) — guarda los bytes en tu propio almacenamiento, nunca enlaces directamente la URL de la tarea.

Ejemplo mínimo ejecutable: Python

Script completo con el mismo flujo — crear, sondear, descargar:

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": (
                "El gato se estira, salta del alféizar y camina hacia "
                "la cámara; luz suave de tarde, leve balanceo de cámara en mano"
            ),
            "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()

Las tareas de vídeo tardan mucho más que las de imagen, así que el script sondea cada 10 segundos y establece un límite holgado de 15 minutos. No sondees más agresivamente que cada ~5 segundos — eso no hará que el renderizado sea más rápido.

Escribir prompts que controlen el movimiento

Como no hay parámetros de movimiento, el prompt es tu único volante — y para hailuo-2.3 eso es suficiente. Tres reglas prácticas:

  • Describe el movimiento, no la apariencia. El modelo ya ha visto tu imagen. "una mujer hermosa con un vestido rojo" desperdicia el prompt; "ella gira la cabeza lentamente hacia la izquierda mientras la cámara se acerca" lo aprovecha bien.
  • Indica el movimiento de cámara en lenguaje llano. "acercamiento lento", "órbita a la derecha", "balanceo en mano", "cámara estática" — esto debe escribirse en el texto del prompt, no en el campo (inexistente) camera_movement.
  • Si dudas, deja que prompt_optimizer ayude. Con "prompt_optimizer": true, la plataforma expande prompts breves antes de generar — un buen valor por defecto para entradas cortas. Desactívalo cuando quieras seguir estrictamente una redacción cuidadosamente diseñada.

Consideraciones para producción

Prefiere callbacks al sondeo. Adjunta un callback al crear la tarea y hiapi enviará el resultado del estado terminal por POST a tu endpoint, de modo que no necesitas mantener un bucle abierto:

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

"when": "final" se dispara solo una vez en el estado terminal. El sondeo es adecuado para scripts y notebooks; el callback es la forma correcta para servidores — el renderizado de vídeo es lo bastante largo como para que un worker bloqueado en sondeo sea un coste real.

Gestiona estos errores de forma explícita:

  • 401 con "code": "permission_denied" — la clave tiene un problema, o esa clave no tiene permiso para usar este modelo. El mensaje incluye un request_id que puedes enviar al equipo de soporte.
  • 400 INVALID_REQUEST — violación del schema. El mensaje es preciso, por ejemplo duration: got number, want string o image_url: missing required field "image_url". Regístralo tal cual.
  • 402 al crear la tarea — saldo insuficiente. Ten en cuenta que el vídeo cuesta más por tarea que la imagen, así que una clave que aún ejecuta tareas de imagen puede empezar a fallar al crear vídeos. Recarga saldo o revisa el uso en el dashboard.
  • La tarea pasa a "fail" — fallo del lado de la generación; revisa data.error. Una causa común específica de imagen a vídeo: no se puede obtener image_url (bucket privado, URL firmada caducada).

Sé idempotente frente a reintentos. Si tu proceso muere entre la creación y la descarga, un reinicio ingenuo creará (y pagará) un segundo renderizado. Persiste el taskId en cuanto create_task lo devuelva y, al reiniciar, continúa sondeando en lugar de volver a crear.

Lecturas relacionadas

Preguntas frecuentes

¿Qué duraciones admite imagen a vídeo de hailuo-2.3? Dos: "6" y "10" segundos, pasados como cadenas. Cualquier otro valor — o escribir el mismo valor como número — devuelve 400 INVALID_REQUEST.

¿Por qué "duration": 6 falla y "duration": "6" funciona? El schema define duration como una enumeración de cadenas. La API te lo dirá explícitamente: duration: got number, want string. Es el 400 más común en este endpoint.

¿Puedo establecer la resolución o la relación de aspecto de salida? No. El endpoint no expone los campos resolution ni aspect_ratio; enviar cualquiera de ellos produce un 400 — el schema rechaza propiedades desconocidas en lugar de ignorarlas.

¿Cómo controlo el movimiento de cámara o la intensidad del movimiento? Mediante el prompt. No hay parámetros camera_movement ni motion_strength — escribe las instrucciones de cámara y movimiento en lenguaje llano ("acercamiento lento", "ella camina hacia la cámara").

¿Puedo pasar varias imágenes de referencia? No — image_url es una única cadena, el primer fotograma. Esto difiere de otros modelos en hiapi que aceptan un array image_urls; el schema se define por modelo, así que consulta siempre la página del modelo.

¿Qué formatos de imagen se admiten para el primer fotograma? Formatos web estándar (JPEG/PNG), servidos desde una URL accesible públicamente. Si la plataforma no puede obtener esa URL, la tarea fallará tras crearse — así que verifica que el enlace sea accesible públicamente antes de enviar.

¿En qué se diferencia de hailuo-2.3/text-to-video? Texto a vídeo inventa la escena solo a partir de tu prompt; imagen a vídeo fija el primer fotograma a tu imagen y anima desde ahí. El ciclo de vida de la tarea es idéntico — solo cambia el schema de entrada (aquí image_url es obligatorio, mientras que texto a vídeo no tiene ningún campo de imagen).

¿Cuánto cuesta? El vídeo se factura por segundos de salida y las tarifas cambian — consulta la página del modelo hailuo-2.3/image-to-video y la página de precios para las cifras actuales.

Artículos relacionados

Ver todos los artículos →

Empieza a crear con artefactos de IA persistentes

Consigue tu API key y llama a modelos de imagen, vídeo y audio a través de una única API basada en tasks.

Leer la documentación de la API