HiAPI
Blog

Tutorial

API de conversión de imagen a vídeo de Happyhorse: ejemplos funcionales de curl y Python (hiapi)

Jun 27, 2026
API de conversión de imagen a vídeo de Happyhorse: ejemplos funcionales de curl y Python (hiapi)

Quieres convertir una imagen estática en un vídeo corto y coherente desde tu propio código. El modelo happyhorse-1.1/image-to-video de hiapi hace exactamente eso: pasas una URL de imagen y un prompt de movimiento opcional, y en pocos segundos devuelve un MP4 en 720p o 1080p. Esta guía es el camino funcional más corto: cuerpos de solicitud reales, mensajes de error reales, tanto en curl como en Python, para que puedas publicarlo hoy mismo sin pelearte con los nombres de los campos.

Qué necesitas antes de empezar

  • Una clave de API de hiapi. Consíguela en la consola de hiapi: tiene el aspecto de sk-... y se envía en la cabecera Authorization: Bearer ....
  • Una URL de imagen accesible (HTTPS público). Los archivos locales no sirven; el modelo obtiene la imagen desde esa URL.
  • Una intención de movimiento expresada en texto (opcional pero recomendable). Cuanto más claros sean los verbos, mejor será el efecto de movimiento.

El modelo se factura por segundos de vídeo, con los precios indicados en la página de precios de hiapi; el resto del catálogo de modelos está en hiapi models. Echa un vistazo a ambas antes de aumentar el volumen por lotes.

La solicitud mínima funcional (curl)

Todos los vídeos en hiapi usan el endpoint asíncrono /v1/tasks: primero haces un POST con una solicitud de creación, recibes un taskId y luego consultas periódicamente GET /v1/tasks/{taskId} o recibes una devolución de llamada al completarse.

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": "happyhorse-1.1/image-to-video",
    "input": {
      "image_urls": ["https://example.com/your-still.jpg"],
      "prompt": "the cat slowly turns its head toward the camera and blinks",
      "resolution": "1080p",
      "duration": 5
    }
  }'

Una respuesta correcta tiene este aspecto:

{
  "code": 200,
  "data": { "taskId": "tk-hiapi-01KW3D..." },
  "message": "success"
}

Ahora consulta el resultado (cada 5 segundos es suficiente; la mayoría de los clips terminan en menos de dos minutos):

curl "https://api.hiapi.ai/v1/tasks/tk-hiapi-01KW3D..." \
  -H "Authorization: Bearer sk-your-key"

Mientras se ejecuta, obtendrás "status": "handling". Al completarse, obtendrás:

{
  "code": 200,
  "data": {
    "status": "success",
    "model": "happyhorse-1.1/image-to-video",
    "output": [
      {
        "type": "video",
        "url": "https://temp.hiapi.ai/.../01KW....mp4",
        "expireAt": 1783131245
      }
    ],
    "taskId": "tk-hiapi-01KW3D..."
  }
}

Esa url es lo que debes descargar, y conviene hacerlo de inmediato, porque expireAt es una marca de tiempo Unix que indica cuándo el almacenamiento temporal la eliminará.

Python: consultar hasta que tenga éxito

Este es un script completo y ejecutable: solo tienes que rellenar tu clave y la URL de la imagen.

import os
import time
import requests

API = "https://api.hiapi.ai/v1/tasks"
TOKEN = os.environ["HIAPI_KEY"]  # sk-...

def create_video(image_url: str, prompt: str, duration: int = 5,
                 resolution: str = "1080p") -> str:
    """Submit an image-to-video task. Returns the taskId."""
    r = requests.post(
        API,
        headers={"Authorization": f"Bearer {TOKEN}",
                 "Content-Type": "application/json"},
        json={
            "model": "happyhorse-1.1/image-to-video",
            "input": {
                "image_urls": [image_url],          # exactly one URL
                "prompt": prompt,                   # optional
                "resolution": resolution,           # "720p" or "1080p"
                "duration": duration,               # int, 3..15
            },
        },
        timeout=60,
    )
    body = r.json()
    if body.get("code") != 200:
        raise RuntimeError(f"create failed: {body}")
    return body["data"]["taskId"]

def wait_for_video(task_id: str, timeout_s: int = 600,
                   poll_s: int = 5) -> str:
    """Block until the task succeeds, fails, or times out. Returns the MP4 URL."""
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(
            f"{API}/{task_id}",
            headers={"Authorization": f"Bearer {TOKEN}"},
            timeout=30,
        )
        task = r.json().get("data") or {}
        status = task.get("status")
        if status == "success":
            return task["output"][0]["url"]
        if status == "fail":
            raise RuntimeError(f"task failed: {task.get('error')}")
        time.sleep(poll_s)
    raise TimeoutError(f"task {task_id} did not finish in {timeout_s}s")

def download(url: str, path: str) -> None:
    with requests.get(url, stream=True, timeout=120) as r:
        r.raise_for_status()
        with open(path, "wb") as f:
            for chunk in r.iter_content(1 << 16):
                f.write(chunk)

if __name__ == "__main__":
    task_id = create_video(
        image_url="https://example.com/your-still.jpg",
        prompt="the dancer spins once and ends in a pose",
        duration=5,
        resolution="1080p",
    )
    print("submitted:", task_id)
    mp4_url = wait_for_video(task_id)
    download(mp4_url, "out.mp4")
    print("saved out.mp4")

En total son unas 50 líneas, y esa es toda la superficie de la API que necesitas para producción: sin SDK, sin colas en segundo plano, sin protocolos de streaming. Ejecútalo con HIAPI_KEY=sk-... python video.py.

Producción: usa devoluciones de llamada en lugar de consultas periódicas

La consulta periódica funciona bien para tareas puntuales y pruebas. Para cargas de servidor, lo que quieres es que la plataforma te llame cuando el clip esté listo: elimina el bucle, escala sin trabajo adicional y el flujo de solicitudes vuelve de inmediato.

Añade un objeto callback en el nivel raíz del cuerpo de la solicitud (no dentro de input):

curl -X POST "https://api.hiapi.ai/v1/tasks" \
  -H "Authorization: Bearer sk-your-key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "happyhorse-1.1/image-to-video",
    "input": {
      "image_urls": ["https://example.com/still.jpg"],
      "prompt": "slow zoom out from the subject",
      "resolution": "1080p",
      "duration": 6
    },
    "callback": {
      "url": "https://your-app.example.com/webhooks/hiapi",
      "when": "final"
    }
  }'

Cuando la tarea alcanza un estado terminal, hiapi hace un POST a tu URL con el mismo objeto de tarea que habrías obtenido al consultar (status, output[].url, error, etc.). when: "final" significa que solo recibirás devoluciones de llamada de éxito o fallo, sin ruido de progreso.

Algunas reglas que te ahorrarán tiempo real de depuración:

  • Haz que el webhook sea idempotente. El mismo taskId puede llegar dos veces (los reintentos de red existen). Consulta ese task en la base de datos antes de procesarlo.
  • Realoja el MP4 dentro de la función de procesamiento. La URL que te da hiapi apunta a almacenamiento temporal con un campo expireAt; guárdalo en streaming en tu propio bucket en cuanto lo recibas, o lo perderás.
  • Confirma rápido. Devuelve un 2xx rápidamente y haz el trabajo pesado de forma asíncrona. Los webhooks lentos corren riesgo de reintentos.
  • Usa la consulta periódica como respaldo. Envuelve el taskId en una tarea diferida y consulta de nuevo a los 10 minutos por si se pierde el webhook. Si te ocurre con frecuencia, consulta las razones por las que la devolución de llamada de tareas de hiapi puede no dispararse para ver las causas habituales.

Mensajes de error que encontrarás

Estas son las cadenas exactas que devuelve la API; hacer coincidir con ellas en tu código es más fiable que adivinar.

Situación HTTP Forma del cuerpo de respuesta
Clave incorrecta o revocada 401 {"error":{"code":"permission_denied","message":"This API key cannot use the selected model...","type":"hiapi_error"}}
Falta un campo obligatorio 400 {"code":400,"error_code":"INVALID_REQUEST","message":"invalid input: image_urls: missing required field \"image_urls\""}
resolution no válido 400 ... resolution: value must be one of '720p', '1080p'
Duración fuera de rango 400 ... duration: minimum: got 2, want 3 / duration: maximum: got 30, want 15
Longitud incorrecta de image_urls 400 ... image_urls: maxItems: got 2, want 1 (o minItems: got 0, want 1)
Campo desconocido en la raíz o dentro de input 400 ... additional properties 'identity_preserving' not allowed
La tarea falla después del envío 200 (GET de la tarea) y data.status == "fail" junto con data.error.{code,message}

Los errores de autenticación y validación significan que no debes reintentar; todos los demás casos (5xx de la plataforma, tiempos de espera de red durante la consulta periódica, task fail transitorios con códigos reintentables) son adecuados para un reintento con retroceso ligero.

Ajustar la salida

Hay cuatro parámetros que realmente influyen; cualquier otra cosa (seed, aspect_ratio, fps, cfg_scale, negative_prompt) se rechaza actualmente como campo desconocido. Mantente dentro de este conjunto:

  • image_urls: una URL HTTPS. La relación de aspecto y la composición de la salida siguen a esta imagen.
  • prompt: una descripción breve del movimiento ("she smiles and tilts her head"). Los prompts cinematográficos basados en verbos superan a las listas de adjetivos.
  • resolution: "720p" para prototipos y miniaturas, "1080p" para entrega final.
  • duration: segundos enteros, de 3 a 15. Los clips más largos cuestan más tiempo y dinero; elige la duración más corta que cuente la historia.

Para proyectos que necesitan un fotograma inicial y uno final, consulta happyhorse-1.1 reference-to-video. Para texto a vídeo puro que no requiere una imagen de origen, happyhorse-1.1 text-to-video es la guía hermana. Explora el resto en el catálogo de modelos de hiapi o en la sección de recetas del blog.

Preguntas frecuentes

¿Puedo pasar dos imágenes para controlar el primer y el último fotograma? No con happyhorse-1.1/image-to-video: image_urls admite como máximo un elemento (maxItems: 1). Si necesitas esto, usa happyhorse-1.1/reference-to-video o constrúyelo con happyhorse-1.1 reference-to-video.

¿Qué resolución tiene realmente el MP4 que recibo? La salida sigue la relación de aspecto de tu imagen de entrada y el indicador resolution que establezcas. "1080p" da la más alta de las dos; "720p" es más rápido y barato para pruebas.

¿Por qué mi clave funciona con otros modelos pero recibo un 401 permission_denied? Algunos modelos están restringidos por clave. Consulta la página del modelo en la consola de hiapi; si happyhorse-1.1/image-to-video no está en tu lista de permitidos, genera una nueva clave o contacta con soporte indicando el request_id del error.

¿Hay respuestas en streaming/SSE? No. Todos los modelos de vídeo usan el patrón de tarea asíncrona descrito en este artículo. Elige entre consulta periódica o devolución de llamada según si controlas el servidor.

¿Dónde veo el coste real de cada clip? En la página de precios de hiapi. El coste se calcula por segundos de vídeo, así que el duration que pases determina directamente el gasto; mantén los clips de desarrollo/prueba en duration: 3 para ahorrar presupuesto.

La URL de salida deja de funcionar al cabo de un día. Es el comportamiento esperado: la URL pertenece a almacenamiento temporal y la respuesta incluye una marca de tiempo Unix expireAt. Descarga siempre el MP4 en el momento en que la tarea tenga éxito (o haz streaming a través de tu propia CDN).

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