HiAPI
Blog

Tutorial

Cómo usar omnihuman-1.5 a través de la API de hiapi: curl, Python y solicitudes listas para ejecutar

Oct 14, 2026
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

  1. 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.
  2. 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 de localhost y 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 200 con data.status == "fail" y data.error. La causa más habitual es que el servidor no pueda descargar image_url/audio_url. Comprueba que ambas sean de acceso público y sigan siendo válidas. Ramifica siempre según data.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

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 →

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