Tutorial
Cómo usar omnihuman-1.5 a través de la API de hiapi: curl, Python y solicitudes listas para ejecutar
omnihuman-1.5 toma el retrato de una persona y una pista de audio, y genera un video de la persona hablando con sincronización labial. En hiapi, al igual que todos los demás modelos multimedia, se ejecuta sobre el mismo protocolo de tareas asíncronas: primero llama una vez a POST /v1/tasks para crear la tarea y luego sondea (o recibe el webhook) hasta que la URL del video esté lista. Esta guía utiliza el esquema de solicitud real, verificado campo por campo con la API en producción, por lo que el código curl y Python de abajo se puede copiar y ejecutar directamente, no es solo un ejemplo.
Qué vas a construir
Un script que envía una imagen de rostro y una pista de voz a omnihuman-1.5 y devuelve un archivo MP4 de ese rostro hablando y con los labios sincronizados según el audio. La duración del clip sigue a tu audio; no hay ningún campo duration que configurar.
Requisitos previos
- Una clave de API de hiapi. Créala en el dashboard; se envía como token Bearer en cada solicitud. Consulta la documentación de autenticación para ver el formato de los encabezados.
- Una URL de imagen y una URL de audio de acceso público. omnihuman-1.5 descarga ambas en el servidor, por lo que sirve cualquier URL
http(s)que tu máquina pueda abrir de forma anónima; las rutas delocalhosty las URL firmadas que caducan a mitad de la tarea no funcionan.
Eso es todo: no se necesita SDK y, aparte de la clave, no se necesita ninguna cuenta.
Esquema de entrada
omnihuman-1.5 acepta un objeto input estricto. Los campos desconocidos se rechazan, por lo que estos son todos los campos disponibles:
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
image_url |
string | Sí | Una URL de retrato. Un único rostro claro y frontal funciona mejor. |
audio_url |
string | Sí | La pista de voz para la sincronización labial. Determina la duración de la salida. |
prompt |
string | No | Guía opcional de movimiento/expresión. |
resolution |
string | No | "720p" o "1080p". |
No existe un modo de solo texto: audio_url es obligatorio. Si solo tienes un guion, primero genera la voz (con un modelo TTS, o con un avatar digital impulsado por texto como heygen-avatar-v) y luego pasa aquí la URL del audio generado.
Ejemplo mínimo ejecutable (curl)
Crear la tarea:
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "omnihuman-1.5",
"input": {
"image_url": "https://example.com/portrait.jpg",
"audio_url": "https://example.com/voiceover.mp3",
"resolution": "720p"
}
}'
La respuesta incluye el id de la tarea:
{ "code": 200, "data": { "taskId": "b3f1c2e8-...", "status": "pending" } }
Sondea la tarea hasta que alcance un estado terminal:
curl -s https://api.hiapi.ai/v1/tasks/b3f1c2e8-... \
-H "Authorization: Bearer sk-YOUR_KEY"
Cuando termine, data.status será "success" y el video estará en data.output[0].url:
{
"code": 200,
"data": {
"taskId": "b3f1c2e8-...",
"status": "success",
"output": [
{ "url": "https://temp.hiapi.ai/.../omnihuman.mp4", "expireAt": 1760000000 }
]
}
}
Esa URL de output es temporal (tiene expireAt). Descarga el MP4 en cuanto la tarea termine correctamente; no enlaces en caliente esa dirección en producción.
La misma llamada en Python
import time
import requests # pip install requests
API = "https://api.hiapi.ai/v1/tasks"
KEY = "sk-YOUR_KEY"
HEADERS = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}
def create_task() -> str:
payload = {
"model": "omnihuman-1.5",
"input": {
"image_url": "https://example.com/portrait.jpg",
"audio_url": "https://example.com/voiceover.mp3",
"resolution": "720p",
},
}
r = requests.post(API, headers=HEADERS, json=payload, timeout=60)
r.raise_for_status()
return r.json()["data"]["taskId"]
def wait_for_video(task_id: str, timeout: int = 600, interval: int = 5) -> str:
deadline = time.time() + timeout
while time.time() < deadline:
r = requests.get(f"{API}/{task_id}", headers=HEADERS, timeout=30)
data = r.json()["data"]
status = data["status"]
if status == "success":
return data["output"][0]["url"]
if status == "fail":
raise RuntimeError(f"omnihuman-1.5 task failed: {data.get('error')}")
time.sleep(interval)
raise TimeoutError(f"task {task_id} did not finish in {timeout}s")
if __name__ == "__main__":
tid = create_task()
print("task:", tid)
video_url = wait_for_video(tid)
# URLs expire — persist the bytes immediately.
mp4 = requests.get(video_url, timeout=120).content
with open("talking_head.mp4", "wb") as f:
f.write(mp4)
print("saved talking_head.mp4")
Pasar a producción
Prioriza las devoluciones de llamada en lugar del sondeo intensivo. Añade un objeto callback de nivel superior y hiapi enviará por POST las tareas completadas a tu endpoint en lugar de hacer que sondees:
{
"model": "omnihuman-1.5",
"input": {
"image_url": "https://example.com/portrait.jpg",
"audio_url": "https://example.com/voiceover.mp3",
"resolution": "1080p"
},
"callback": { "url": "https://your.app/webhooks/hiapi", "when": "final" }
}
when solo acepta "final" y se activa tanto en caso de éxito como de fallo, por lo que tu webhook debe ramificar según data.status. El cuerpo entregado es el mismo objeto que se obtiene mediante GET /v1/tasks/{id}. La entrega es de tipo al menos una vez, así que deduplica por taskId.
Nunca reenvíes ante errores temporales. Si una solicitud de sondeo agota el tiempo de espera o se interrumpe la conexión, vuelve a sondear el mismo taskId; no vuelvas a llamar a POST /v1/tasks. Reenviar inicia una segunda tarea facturable. Guarda el taskId en cuanto lo obtengas.
Gestiona por separado las tres formas de fallo:
- Fallo de autenticación → HTTP
401, cuerpo{"error": {"code": "permission_denied", "type": "hiapi_error", ...}}. Corrige la clave; no reintentes. - Solicitud incorrecta (campos ausentes o no válidos) → HTTP
400, cuerpo{"code": 400, "error_code": "INVALID_REQUEST", "message": "..."}. El mensaje indica el campo problemático; corrígelo y vuelve a enviar. - Tarea iniciada pero fallida → HTTP
200condata.status == "fail"ydata.error. La causa más habitual es que el servidor no pueda descargarimage_url/audio_url. Comprueba que ambas sean de acceso público y sigan siendo válidas. Ramifica siempre segúndata.status, no solo según el código de estado HTTP.
El precio se factura por segundos de salida y se indica en la página de precios; como la duración del clip sigue a tu audio, el coste varía según la duración de este.
Guías relacionadas
- Introducción a la API asíncrona unificada: el contrato de creación y sondeo de
/v1/tasksque comparten todos los modelos. - Inicio rápido: completa de principio a fin tu primera solicitud a hiapi.
- Tutorial de la API de HeyGen Avatar IV: otro modelo de foto a video hablado, útil para comparar enfoques de sincronización labial.
- HeyGen Avatar V: un avatar digital impulsado por guion (texto a video) para cuando no tengas un archivo de audio.
Preguntas frecuentes
¿Puedo controlar omnihuman-1.5 con texto en lugar de un archivo de audio?
No. audio_url es obligatorio y el modelo sincroniza los labios con ese audio. Si necesitas un flujo de guion a video, primero sintetiza la voz (con cualquier TTS) o usa un avatar digital impulsado por texto como heygen-avatar-v, y luego pasa aquí la URL del audio.
¿Cuánto dura el video de salida?
Sigue a tu pista de audio; no hay ningún parámetro duration. Una pista de voz de 12 segundos generará aproximadamente 12 segundos de video.
¿Qué tipo de imagen funciona mejor? Un retrato individual, bien iluminado y frontal, en una URL pública. Las fotos de grupo o con el rostro muy obstruido producen una sincronización labial deficiente.
¿720p o 1080p?
Ambas son válidas para resolution. Empieza con 720p mientras iteres; cambia a 1080p para el render final. Una resolución mayor cuesta más por segundo; consulta precios.
Mi tarea devuelve inmediatamente status: "fail". ¿Por qué?
Casi siempre se debe a que el servidor no puede obtener alguna URL de entrada. Confirma que image_url y audio_url sean de acceso público (ábrelas en una ventana de incógnito) y no hayan caducado. El mensaje data.error ayuda a acotar el problema.
¿Necesito sondear o puedo usar un webhook?
Ambas opciones valen. Para scripts sencillos, sondea con GET /v1/tasks/{id}; en producción, registra callback.url y establece when en "final" para no tener que mantener un bucle de sondeo.
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