Tutorial
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/taskscon"model": "hailuo-2.3/image-to-video"en el cuerpo de la solicitud, luego sondea conGET /v1/tasks/<taskId>hasta que la tarea alcance un estado terminal. - Dos campos de entrada obligatorios:
prompteimage_url— una única URL pública que apunta a tu primer fotograma (no un array). durationes una cadena, no un número:"6"o"10". Enviar el entero6será rechazado con 400.- El schema es estricto. No hay parámetros
motion_strength,camera_movementniresolution— 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:
- Una clave de API de hiapi. Crea una en el dashboard. La clave tiene la forma
sk-...y se coloca en la cabeceraAuthorization: Bearer. - 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 rutalocalhosto 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_optimizerayude. 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:
401con"code": "permission_denied"— la clave tiene un problema, o esa clave no tiene permiso para usar este modelo. El mensaje incluye unrequest_idque puedes enviar al equipo de soporte.400 INVALID_REQUEST— violación del schema. El mensaje es preciso, por ejemploduration: got number, want stringoimage_url: missing required field "image_url". Regístralo tal cual.402al 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; revisadata.error. Una causa común específica de imagen a vídeo: no se puede obtenerimage_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
- Página del modelo hailuo-2.3/image-to-video — playground, facturación por segundo y schema en vivo.
- hailuo-2.3-fast/image-to-video — variante de baja latencia, mismo flujo de trabajo de tareas.
- hailuo-2.3/text-to-video — sin imagen de origen, genera solo a partir del prompt.
- Documentación de hiapi — descripción completa de la interfaz de tareas, incluidos los callbacks.
- Precios — tarifas actuales de vídeo por segundo para todos los modelos.
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 →-
Tutorial
Cómo llamar a hailuo-2.3-fast/image-to-video a través de la API de HiAPI: práctica con curl y Python
La primera solicitud ejecutable del nivel rápido y de bajo coste de imagen a vídeo: del ID del modelo a la descarga del MP4
-
Tutorial
Cómo llamar a hailuo-2.3/text-to-video mediante la API de HiAPI: curl, Python y ejemplos de solicitud ejecutables
-
Tutorial
Uso de Kandinsky 6.0 Pro texto a video en HiAPI: tutorial con curl y Python
Demostración práctica centrada en la solicitud: construir la llamada de prompt a clip de video, enviar la solicitud, sondear el estado de finalización y descargar el resultado