HiAPI
Blog

Tutorial

API de generación de video por referencia HappyHorse 1.1: tutorial de curl + Python listo para ejecutar

Jun 26, 2026
API de generación de video por referencia HappyHorse 1.1: tutorial de curl + Python listo para ejecutar

HappyHorse 1.1 R2V puede convertir hasta nueve imágenes de referencia más un prompt de texto en un video corto, manteniendo consistentes el sujeto, la ropa y el estilo de la escena entre varios planos. En HiAPI, este modelo se ofrece a través de un endpoint unificado de tareas asíncronas, con el ID de modelo happyhorse-1.1/reference-to-video. Este tutorial primero muestra la solicitud mínima que funciona y luego presenta la versión de producción con callback.

Objetivo y requisitos previos

Completarás las siguientes operaciones:

  1. Subir (o alojar ya) de 1 a 9 imágenes de referencia en URL HTTPS accesibles por la API.
  2. Enviar por POST a https://api.hiapi.ai/v1/tasks una tarea que haga referencia a esas URL.
  3. Cuando la tarea alcance el estado success, leer la dirección MP4 final desde data.output[0].url.

Requisitos previos:

  • Una cuenta de HiAPI y una API Key que empiece por sk-. Puedes obtenerla desde la consola de HiAPI y consultar la documentación de autenticación para conocer las reglas de las cabeceras. La clave debe enviarse como Authorization: Bearer sk-... y nunca debe incluirse en el código del cliente.
  • Requisitos de alojamiento de imágenes: cada imagen de referencia debe estar en formato JPEG, PNG o WEBP, con el lado más corto ≥ 400px y ≤ 20MB. Se pueden usar URL de CDN públicas; también se aceptan URL firmadas de S3 siempre que el ejecutor de tareas pueda acceder a ellas.

Solicitud mínima funcional (curl)

Este es el cuerpo de solicitud mínimo que acepta el schema. Usa la salida predeterminada de 1080p / 16:9 / 5 segundos, por lo que solo necesitas establecer los campos prompt y reference_image.

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/reference-to-video",
    "input": {
      "prompt": "El personaje de [Image 1] camina por una escena con el estilo de [Image 2], toma cinematográfica",
      "reference_image": [
        "https://static.hiapi.ai/example/ref-1.jpg",
        "https://static.hiapi.ai/example/ref-2.jpg"
      ]
    }
  }'

La respuesta es el sobre de la tarea; el único campo que realmente necesitas es data.taskId:

{
  "code": 200,
  "data": { "taskId": "task_xxx", "status": "queued" }
}

Consulta repetidamente hasta el estado terminal:

curl https://api.hiapi.ai/v1/tasks/task_xxx \
  -H "Authorization: Bearer sk-YOUR_KEY"

Cuando data.status pase a success, la URL del video estará en data.output[0].url. Esa URL incluye una marca de tiempo expireAt, así que descarga el archivo de inmediato o transfiérelo a un almacenamiento persistente: no enlaces directamente esa dirección desde tu sitio web.

Parámetros

El endpoint R2V valida estrictamente la entrada; los campos desconocidos devuelven 400 INVALID_REQUEST. Schema completo (validado en vivo contra POST /v1/tasks):

Campo Tipo Obligatorio Valores permitidos Valor predeterminado
model string Sí happyhorse-1.1/reference-to-video (valor fijo) -
input.prompt string Sí Usa [Image 1], [Image 2]… para referenciar las imágenes de referencia, en el mismo orden que reference_image -
input.reference_image string[] Sí 1-9 URL HTTPS, formato JPEG/PNG/WEBP, lado más corto ≥ 400px, cada una ≤ 20MB -
input.resolution enum No 720p, 1080p 1080p
input.aspect_ratio enum No 16:9, 9:16, 3:4, 4:3, 4:5, 5:4, 1:1, 9:21, 21:9 16:9
input.duration integer No 3-15 (segundos) 5
callback.url string No URL HTTPS que recibe los eventos de estado terminal de la tarea -
callback.when enum No final (único valor actual) final

Hay algunas reglas de validación poco intuitivas que vale la pena conocer antes de que te causen problemas:

  • Incluso si solo hay una URL de imagen de referencia, pasa reference_image como array. Pasar directamente una cadena devuelve reference_image: got string, want array.
  • duration debe ser un entero entre 3 y 15 (inclusive). Tanto 999 como 1 devuelven un mensaje de error de límite explícito.
  • En el nivel superior no existen seed, negative_prompt, fps, cfg_scale ni steps. La API rechaza propiedades desconocidas.

Para conocer el precio completo según la combinación de resolución y duración, consulta la página de precios de HiAPI: el costo varía según los parámetros resolution y duration al mismo tiempo.

Python: de extremo a extremo + polling

El siguiente bloque de código es un generador autocontenido que envía la tarea, consulta hasta el estado terminal y descarga el video. Se ha hecho deliberadamente sin dependencias (solo requests), para que puedas copiarlo en cualquier servicio. Para una explicación más completa del ciclo de vida de las solicitudes, consulta la guía de inicio rápido.

import os
import time
import requests

API = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"}


def submit_r2v(prompt: str, reference_images: list[str], duration: int = 5,
               resolution: str = "1080p", aspect_ratio: str = "16:9") -> str:
    if not 1 <= len(reference_images) <= 9:
        raise ValueError("reference_image must contain 1-9 URLs")
    payload = {
        "model": "happyhorse-1.1/reference-to-video",
        "input": {
            "prompt": prompt,
            "reference_image": reference_images,
            "resolution": resolution,
            "aspect_ratio": aspect_ratio,
            "duration": duration,
        },
    }
    r = requests.post(API, json=payload, headers=HEADERS, timeout=60)
    r.raise_for_status()
    data = r.json()
    if data.get("code") != 200:
        raise RuntimeError(f"create failed: {data}")
    return data["data"]["taskId"]


def wait_for_video(task_id: str, timeout_s: int = 600, poll_s: int = 5) -> str:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(f"{API}/{task_id}", headers=HEADERS, timeout=30)
        r.raise_for_status()
        task = r.json()["data"]
        status = task["status"]
        if status == "success":
            return task["output"][0]["url"]
        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} did not finish in {timeout_s}s")


def download(url: str, path: str) -> None:
    with requests.get(url, stream=True, timeout=300) 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 = submit_r2v(
        prompt="Mantén el diseño del sujeto de [Image 1], caminando dentro de la atmósfera de [Image 2]",
        reference_images=[
            "https://static.hiapi.ai/example/char.jpg",
            "https://static.hiapi.ai/example/mood.jpg",
        ],
        duration=5,
        resolution="720p",
        aspect_ratio="9:16",
    )
    print("submitted", task_id)
    video_url = wait_for_video(task_id)
    print("ready", video_url)
    download(video_url, "out.mp4")

Este código hace deliberadamente lo siguiente:

  • Lee la clave desde la variable de entorno HIAPI_API_KEY, para que nunca acabe en git ni en los logs del contenedor.
  • Usa r.raise_for_status() para capturar errores de transporte antes de analizar el JSON.
  • Considera terminales solo success y fail; todos los demás estados (queued, processing, etc.) hacen continuar el bucle.
  • Descarga la URL de salida de inmediato. Las URL de salida son direcciones firmadas y caducan; si necesitas el archivo más tarde, transfiérelo a un almacenamiento persistente o cópialo en tu propio bucket de almacenamiento.

Producción: pasar del polling a webhook

Consultar cada 5 segundos está bien para un portátil o un worker de bajo tráfico. Para un servicio real, registra un callback al crear la tarea y deja que HiAPI envíe los eventos de estado terminal a tu endpoint: así tu worker puede permanecer inactivo y ya no tendrás que pagar por solicitudes GET vacías. Para el schema completo de la solicitud, consulta crear tarea, y para el polling de respaldo, consulta obtener detalles de la tarea.

payload = {
    "model": "happyhorse-1.1/reference-to-video",
    "input": {
        "prompt": "El personaje de [Image 1] camina por la calle de la ciudad de [Image 2]",
        "reference_image": [
            "https://cdn.example.com/char.jpg",
            "https://cdn.example.com/street.jpg",
        ],
        "resolution": "1080p",
        "aspect_ratio": "16:9",
        "duration": 8,
    },
    "callback": {
        "url": "https://your-domain.com/hiapi/callback",
        "when": "final",
    },
}

El endpoint receptor debe implementar dos cosas:

  1. Idempotencia por taskId. Tanto success como fail son estados terminales; los reintentos de HiAPI (o los duplicados generados por tu propia capa de reintentos) reenviarán el mismo taskId. Deduplica antes de desencadenar cualquier efecto secundario (copia a S3, mensaje de Slack, evento de facturación).
  2. Polling de verificación de respaldo. Si tu endpoint está caído, el callback se pierde. Mantén una tarea de verificación ligera que llame a GET /v1/tasks/<id> para todas las tareas en estado processing que superen el peor tiempo de generación.

Errores que realmente encontrarás

Estos merecen un tratamiento explícito; el resto son 5xx transitorios que el SDK o tu cliente HTTP deben reintentar con retroceso exponencial.

  • 401 permission_denied: la cabecera Authorization falta, tiene formato incorrecto o la clave ha sido revocada. Vuelve a emitir la clave desde la consola en lugar de parchear la cabecera.
  • 400 INVALID_REQUEST con ruta de campo: el mensaje de error te indica exactamente dónde falló, por ejemplo reference_image: maxItems: got 10, want 9 o aspect_ratio: value must be one of .... Valida la entrada antes de que el worker la envíe, para fallar rápido en local.
  • task failed (estado terminal): el polling/callback devuelve status: "fail", junto con error.code y error.message. Causas comunes: una URL de imagen de referencia que el worker no puede obtener, activación de la moderación NSFW, o una imagen cuyo lado más corto está por debajo del umbral de 400px.

Preguntas frecuentes

¿Se pueden referenciar más de 9 imágenes? No. El schema limita reference_image a un máximo de 9. Si necesitas más variaciones, combina fuera de la plataforma una imagen de referencia de mayor densidad (una cuadrícula o una hoja de storyboard) y úsala como una sola entrada.

¿Es obligatorio usar las marcas [Image N] en el prompt? Esas marcas son tu forma de señalar una imagen de referencia concreta. Si nunca indicas imágenes en el prompt también es válido, pero el modelo mezclará libremente todas las imágenes de referencia, lo que para escenarios de consistencia entre varios planos normalmente no es lo que deseas.

¿Por qué recibo 503 nsfw_moderation_unavailable? Es un error temporal de la plataforma causado por el servicio de moderación, no está relacionado con tu solicitud. Reintenta tras un breve retroceso.

¿Debo usar 720p o 1080p? 1080p es el valor predeterminado y ofrece mejor calidad, pero cuesta más y tarda más en renderizar. Para clips verticales de redes sociales que de todos modos el reproductor reducirá, 720p con aspect_ratio: 9:16 suele ser un mejor equilibrio.

¿Se puede pasar seed para hacer la salida reproducible? Actualmente no. La API trata seed como una propiedad desconocida y la rechaza. Si necesitas reproducibilidad, fija exactamente prompt, reference_image, aspect_ratio, resolution y duration, y acepta cierta variación entre ejecuciones.

¿Dónde se almacenan las URL de salida y cuánto tiempo son válidas? Son URL firmadas con un TTL corto; la respuesta incluye expireAt. Tras el éxito de la tarea, descárgalas de inmediato o transfiérelas a un almacenamiento persistente.

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