Tutorial
Cómo usar kling-3.0-omni/image-to-video a través de la API de HiAPI: guía con curl y Python
Qué hace el endpoint image-to-video
La generación de video no es una llamada síncrona. En HiAPI, kling-3.0-omni/image-to-video sigue un patrón de enviar primero, consultar después: envías una solicitud HTTP que describe la tarea, la API acepta la solicitud y devuelve un identificador de tarea, y luego consultas dicha tarea hasta que la generación del video finaliza.
Esta estructura de dos pasos tiene una razón sólida. Renderizar un video a partir de una imagen estática requiere tiempo, mucho más del que un típico solicitud HTTP debería permanecer abierta. Si el endpoint se bloqueara hasta que el archivo esté listo, cada llamada sufriría tiempos de espera de la puerta de enlace y cortes de conexión. Al devolver inmediatamente un identificador de tarea, la API deja que tu código decida cuánto esperar, con qué frecuencia comprobar y cómo manejar los errores.
Una tarea típica contiene tres partes lógicas:
- Medios de entrada: la imagen (o conjunto de imágenes) que ancla el plano.
- Ajustes de generación: el texto del prompt, opciones relacionadas con el movimiento, audio y selección de resolución.
- Ciclo de vida: la respuesta de envío que contiene el ID de la tarea, seguida de comprobaciones de estado hasta obtener la URL del video final o el motivo del fallo.
Ayuda pensar en este endpoint como una cola con recibo. La llamada de envío es cuando entregas la orden; el recibo es el ID de la tarea; la llamada de consulta es cuando preguntas en el mostrador si tu pedido está listo. En el envío no se devuelve ningún video en sí, así que cualquier cliente que escribas debe organizarse desde el principio en torno a estas dos fases.
De ello se derivan dos consecuencias. Primera, tu manejo de errores debe ser separado: un cuerpo de solicitud rechazado es un problema en el envío, corregible en el código; mientras que un fallo de renderizado es un problema a nivel de tarea, que no se resuelve por muchas veces que reenvíes el mismo cuerpo. Segunda, necesitas algún lugar donde guardar el ID de la tarea —una línea de registro, una fila de base de datos, una variable en el script—, porque es el único asa que tienes sobre el trabajo en curso.
Con este modelo mental establecido, el resto de esta guía es principalmente trabajo de plomería: construir la solicitud y luego esperarla correctamente.
Construir una solicitud curl funcional
Empieza con la solicitud más pequeña que funcione, confirma que se acepta y solo entonces añade opciones. Los nombres exactos de los campos JSON y la ruta del endpoint se definen en la página de referencia de este modelo en HiAPI; consulta esa página para obtener el esquema actual en lugar de fiarte de fragmentos de artículos de blog (incluido este).
La llamada de envío tiene una estructura predecible. Te autenticas con tu clave de API de HiAPI en la cabecera Authorization, declaras Content-Type: application/json y envías el cuerpo JSON. Conceptualmente, el cuerpo es así:
{
"model": "kling-3.0-omni/image-to-video",
"input": {
"image": "https://your-cdn.example.com/frame-first.jpg",
"prompt": "A slow dolly-in as morning light crosses the room"
}
}
La estructura importa más que los valores literales: el identificador del modelo, el objeto de entrada que lleva la referencia a la imagen de origen y el prompt, y el espacio para las opciones de generación. Antes de codificar cada clave, verifícala contra la documentación actual.
La llamada curl equivalente sigue el patrón habitual:
curl -sS -X POST "$HIAPI_BASE_URL/v1/video/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d @request.json
Guardar el cuerpo en request.json en lugar de en línea hace que iterar sea mucho más sencillo: editas un archivo, no una entrada del historial del shell.
Leer la respuesta
Un envío exitoso es deliberadamente anodino. Espera obtener un identificador de tarea y un estado que indique aceptado pero no completado. Analízalo, imprímelo y guárdalo:
curl -sS -X POST "$HIAPI_BASE_URL/v1/video/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d @request.json | tee submit.json
Comprueba primero el código de estado HTTP. 4xx significa que la solicitud en sí fue rechazada: clave incorrecta, JSON mal formado, opciones no compatibles, URL de imagen no válida. 2xx significa que la tarea entró en el sistema y, desde ese momento, la señal útil proviene del estado de la tarea, no del código de estado HTTP.
Antes de escribir cualquier código de consulta, ejecuta un envío manual una vez. Confirma que puedes ver el ID de la tarea en la salida. Este ida y vuelta manual detecta la mayoría de los errores de configuración desde el principio.
Manejar entradas de primer y último fotograma
La imagen que envías no es solo una referencia: restringe el punto de partida del plano. Esta distinción determina la mayor parte de las decisiones de diseño de esta sección.
Solo primer fotograma
Cuando proporcionas una sola imagen, trátala como el fotograma inicial. El prompt describe entonces qué debe ocurrir a partir de ese fotograma: movimiento de cámara, acción del sujeto, cambios de iluminación, ritmo. Como el modelo debe crear todo lo que viene después del primer fotograma, los prompts que describen una trayectoria funcionan mejor que los que describen una escena estática. «Mantente quieto, te ves precioso» no le da al modelo nada que animar; «ella se gira hacia la ventana y la luz se calienta con ella» le da dirección.
Añadir un último fotograma
La entrada de último fotograma convierte la tarea en un problema de interpolación. Proporcionas dos extremos y el modelo genera la trayectoria entre ellos. Esto es muy potente para transiciones controladas —un producto que gira de la vista frontal a la trasera, un personaje que cambia entre dos poses— porque el punto final queda fijado, no imaginado.
Tres notas prácticas:
- Mantén ambos fotogramas compatibles. Encuadres, perspectivas y proporciones del sujeto similares hacen creíble la interpolación. Composiciones muy dispares obligan al modelo a inventar un puente que puede no existir.
- Reescribe el prompt. Con dos fotogramas, tu texto debe describir el movimiento entre ellos, no los extremos. Nombrar un estilo de movimiento y una duración resulta más natural que narrar el estado final que ya has proporcionado.
- Espera que la estructura de la solicitud cambie. El último fotograma es un campo de entrada aparte, así que tu cuerpo de solicitud tendrá una clave más. Construye el objeto de solicitud de modo que los campos opcionales puedan añadirse sin reestructurarlo todo.
En el código, trata los fotogramas como un pequeño conjunto de entradas con nombre, no como una colección suelta de URL:
inputs = {"image": first_frame_url}
if last_frame_url:
inputs["last_frame"] = last_frame_url
Esta comprobación condicional mantiene las llamadas de un solo fotograma y de dos fotogramas en la misma ruta de código, lo cual importa en cuanto empiezas a generar variantes.
Por último, aloja los fotogramas en un lugar fiablemente accesible. Una URL que caduca a mitad de la tarea fallará después del envío, y el error parecerá un fallo de generación en lugar de un problema de recursos.
Configurar el audio y la resolución de salida
El audio y la resolución son elecciones a nivel de solicitud, no ajustes globales. Los decides por tarea, lo que significa que deben estar en el constructor del cuerpo de la solicitud, junto al prompt, y no en una constante de configuración que se establece una vez y se olvida.
Audio
Las opciones de audio normalmente te permiten solicitar una banda sonora junto con el video, u omitirla. Trátalo como una decisión de modelado relacionada con el caso de uso: un clip corto para redes sociales puede beneficiarse del audio generado, mientras que un plano para el que vas a poner música en el editor quizá no deba llevar audio. Solicitar audio que nunca usarás desperdicia tiempo de generación sin ningún beneficio.
Como el comportamiento del audio es uno de los campos con más probabilidades de evolucionar, no codifiques de forma rígida suposiciones sobre el contenido de la pista o su relación con el prompt. Lee la documentación actual del modelo y luego valida la salida con una tarea real antes de diseñar cualquier flujo posterior en torno a ella.
Resolución
Una resolución mayor significa más píxeles por fotograma y una cantidad de cómputo significativamente mayor. Un flujo de trabajo práctico: iterar con una resolución moderada mientras ajustas el prompt y la selección de fotogramas, y luego regenerar la configuración ganadora a la resolución final. Regenerar un buen resultado es mucho más barato que descubrir al final que tu mejor resultado se produjo con los ajustes equivocados.
Algunos hábitos que vale la pena adoptar:
- Valida antes de enviar. Si el modelo no admite la resolución que pasas, quieres un rechazo claro en el envío, no un fallo confuso a nivel de tarea varios minutos después.
- Guarda los ajustes junto con la tarea. Cuando compares varios resultados, necesitas saber con qué resolución y elección de audio se produjo cada uno.
- No asumas equivalencia entre fotogramas. Una tarea de dos fotogramas con imágenes de origen grandes puede comportarse de forma distinta a una tarea de un solo fotograma; prueba ambas rutas.
Dónde deben ir en el código
Agrupa las opciones de generación en su propio objeto para que la intención sea evidente y puedas sustituir los valores predeterminados en un solo lugar:
options = {
"resolution": "1080p", # confirm supported values in the model docs
"audio": True,
}
Añade comentarios a las líneas que dependen de valores de la documentación. Tú del futuro —o un compañero de equipo— debería poder distinguir de inmediato qué campos son seguros y cuáles hay que comprobar contra la página de referencia antes de publicar. En caso de duda, la documentación del modelo en HiAPI es la fuente de verdad, no los cuerpos de solicitud de ejemplo.
Consultar el estado de finalización en Python
El código de consulta es donde la mayoría de las integraciones se vuelven frágiles sin que uno se dé cuenta. El objetivo es construir un cliente pequeño y reutilizable que envíe la tarea y la espere con límites razonables, en lugar de un script que se copia, se pega y se edita cada vez.
Estructura mínima del cliente
import os, time, requests
BASE = os.environ["HIAPI_BASE_URL"]
KEY = os.environ["HIAPI_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}"}
def submit(payload: dict) -> str:
r = requests.post(f"{BASE}/v1/video/generations",
headers=HEADERS, json=payload, timeout=30)
r.raise_for_status()
return r.json()["id"]
def wait(task_id: str, interval: float = 5.0, max_wait: float = 600.0) -> dict:
deadline = time.monotonic() + max_wait
while time.monotonic() < deadline:
r = requests.get(f"{BASE}/v1/video/generations/{task_id}",
headers=HEADERS, timeout=30)
r.raise_for_status()
job = r.json()
if job["status"] in ("succeeded", "failed"):
return job
time.sleep(interval)
raise TimeoutError(f"task {task_id} not finished in {max_wait}s")
En ese código hay cuatro detalles que soportan la mayor parte del peso:
- Un tiempo de espera explícito en cada llamada HTTP. Una solicitud colgada es peor que una solicitud fallida.
- Un tiempo máximo de espera. Un bucle sin límite convierte una tarea atascada en un servicio atascado.
- Comprobación de estados terminales. Detente solo cuando el estado sea realmente final; trata todo lo demás como aún en ejecución, incluidos los estados que no reconozcas.
- Devolver el objeto de tarea completo. Quien llama normalmente necesita la URL de salida y cualquier detalle de error, no solo un booleano.
Ritmo de consulta
Para tareas de video, cinco segundos es un valor predeterminado razonable. Consultar cada 200 ms consume cuota sin ningún beneficio, ya que el renderizado tardará minutos de todos modos. Si envías muchas tareas, coloca el bucle de consulta en un worker en lugar de en el manejador de solicitudes, y aplica un ligero retroceso para las tareas de larga duración.
Conectarlo todo
payload = {
"model": "kling-3.0-omni/image-to-video",
"input": {"image": first_frame_url, "prompt": prompt},
"options": options,
}
job = wait(submit(payload))
if job["status"] == "failed":
raise RuntimeError(job.get("error") or "generation failed")
print(job["output"]["url"])
Como submit devuelve una cadena simple y wait devuelve un dict, puedes probar cada mitad de forma independiente: simula la respuesta de envío para probar la lógica de consulta, o llama a submit por separado para comprobar el cuerpo de la solicitud contra la API en vivo.
Depurar fallos habituales
Los fallos se concentran en tres capas, e identificar la capa es la mayor parte del diagnóstico.
Capa 1: rechazo en el envío
Estos aparecen como respuestas 4xx antes de que exista ninguna tarea. Causas habituales:
- Autenticación: cabecera ausente, token bearer mal formado, clave no exportada en el entorno que crees.
- Estructura del cuerpo: campo mal escrito, cadena donde se espera un array, nivel de anidamiento incorrecto en las opciones.
- Valores no admitidos: una resolución o combinación de audio que el modelo no acepta.
- Problemas de recursos: una URL de archivo inaccesible, caducada o que no es una imagen.
Lee el cuerpo de la respuesta de error. La API suele señalar el campo problemático, y la corrección más rápida suele ser la que sugiere el mensaje. Reproduce la llamada fallida en curl para confirmar que el problema está en el cuerpo de la solicitud y no en el código del cliente.
Capa 2: problemas de transporte
La red se interpone entre tu código y el ciclo de vida de la tarea. Los tiempos de espera y los reinicios de conexión en la llamada de envío son ambiguos: la tarea puede haberse creado o no. Si reintentas a ciegas, puedes producir tareas duplicadas.
El patrón seguro es reintentar solo operaciones idempotentes y seguras, y registrar cada intento de envío junto con su respuesta. Si realmente no puedes determinar si el envío tuvo éxito, revisa el historial de tareas antes de reenviar. En cuanto a la consulta, un fallo en una comprobación de estado no equivale a un fallo de la tarea: captura la excepción, espera y vuelve a preguntar.
Capa 3: fallos a nivel de tarea
Estos llegan como estado terminal en una tarea que sí fue aceptada. Normalmente significan que el contenido no puede generarse tal como se especificó: los dos fotogramas difieren demasiado para interpolar, el prompt no se puede conciliar con la imagen de origen, o hubo un error interno durante el renderizado.
Distínguelos claramente en el código, porque las soluciones son distintas. 4xx significa corregir la solicitud. Una tarea fallida significa cambiar la entrada o reintentar: si el mismo cuerpo falla dos veces, deja de reintentar y cambia algo: simplifica el prompt, elige fotogramas más cercanos, reduce la resolución.
Lista de triaje rápida
- ¿El estado HTTP es
4xx? Corrige el cuerpo de la solicitud o las credenciales; no consultes. - ¿El envío tuvo éxito pero la tarea nunca termina? Tu límite de espera es demasiado corto o la tarea está atascada: registra el ID de la tarea e investiga, en lugar de hacer un bucle infinito.
- ¿La tarea falló? Lee los detalles del error y luego ajusta la entrada, en lugar de reenviarla tal cual.
- ¿Tuvo éxito pero el video no es el correcto? Eso es un problema de prompt y selección de fotogramas, no de la API.
Publicar: lista de verificación para producción
Antes de exponerlo a los usuarios, refuerza las partes que fallan en silencio.
Configuración. Guarda la URL base y la clave de API en variables de entorno o en un gestor de secretos, nunca en el código fuente. Si falta cualquiera de las dos, falla rápido al arrancar, no en la primera solicitud de un usuario.
Tiempos de espera y reintentos. Cada llamada saliente necesita un tiempo de espera explícito. Reintenta el envío solo donde la duplicación sea tolerable, y trata los reintentos de consulta como seguros: lecturas de estado idempotentes. Limita el tiempo total de espera y expón un tiempo de espera claro a quien llama.
El ID de la tarea es el registro. Persiste el ID de la tarea junto con el cuerpo de la solicitud enviado: fotogramas, prompt, resolución, elección de audio y marca de tiempo. Cuando alguien pregunte dos semanas después por qué el video se ve mal, esa fila es toda la respuesta.
La salida es temporal. Trata la URL del video devuelta como efímera. En cuanto la tarea tenga éxito, descarga y almacena el video en tu propio almacenamiento persistente, y registra la ubicación de almacenamiento en la tarea.
Conciencia de costes. La generación de video se factura por salida generada, así que la resolución, la duración y el número de generaciones son las palancas importantes. Itera a baja resolución, genera la versión final de forma consciente y registra los ajustes para poder atribuir el gasto a un caso de uso, en lugar de descubrirlo en la factura.
Observabilidad. Emite líneas de registro al enviar y al alcanzar un estado terminal, con el ID de la tarea, la latencia y el estado. Una métrica simple de tasa de éxito por modelo te dirá cuándo los cambios de prompt o los ajustes de opciones empiezan a provocar fallos en las tareas.
Valida contra la documentación, no contra los ejemplos. Antes de cada lanzamiento que implique audio, resolución o entrada de fotogramas, confirma los nombres de los campos y los valores aceptados en la página de referencia del modelo, ya que esos son los ajustes con más probabilidades de cambiar.
Con el cliente, el bucle de consulta y estas salvaguardas, kling-3.0-omni/image-to-video se convierte en un componente predecible en lugar de un script que rezas para que siga funcionando.
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