HiAPI
Blog

Tutorial

API vidu-q3-ad reference-to-video: curl, Python y una solicitud que funciona

Oct 20, 2026
API vidu-q3-ad reference-to-video: curl, Python y una solicitud que funciona

Qué vas a construir

Vas a hacer una llamada de reference-to-video sobre la API asíncrona /v1/tasks de hiapi, usando de 1 a 7 imágenes de producto para convertirlas en un anuncio corto con audio nativo. vidu-q3-ad/reference-to-video es el nivel de Vidu Q3 orientado a publicidad: subes tus imágenes de referencia del producto, escribes una descripción de la toma y el modelo devuelve un MP4 de 3 a 15 segundos con montaje inteligente, movimiento de cámara y efectos de sonido generados; no hay una pista de audio aparte que tengas que aportar.

Solo tienes que enviar un POST, luego hacer polling (o recibir un callback) hasta que la tarea termine y descargar el MP4. Primero obtén tu clave en la consola de hiapi; todas las solicitudes siguientes usan Authorization: Bearer sk-....

Tres datos que debes copiar tal cual:

  • Endpoint de envío: POST https://api.hiapi.ai/v1/tasks
  • Endpoint de polling: GET https://api.hiapi.ai/v1/tasks/{taskId}
  • ID del modelo: vidu-q3-ad/reference-to-video: ni la barra ni el sufijo -ad son opcionales; vidu-q3-mix y vidu-q4-preview son endpoints distintos.

Esquema de entrada (obtenido mediante sondeo en vivo)

Campo ¿Obligatorio? Tipo Descripción
prompt Sí string Descripción de la toma; usa números de orden para referirte a los productos, por ejemplo "el reloj de la imagen de referencia 1"
reference_image_urls Sí array, 1–7 URL pública de la imagen estática del producto (la descarga el servidor)
duration Sí integer, 3–15 Número de segundos; debe ser entero (4.5 se rechaza)
size No enum (10 valores) Determina a la vez la relación de aspecto y el nivel 720/1080; por defecto 1280*720

El validador es muy estricto: cualquier campo fuera de la lista anterior se rechaza directamente. Hay dos tipos de rechazo que conviene señalar, porque hacen tropezar a todo el que migra desde otros modelos de vídeo de hiapi:

  • aspect_ratio y resolution se rechazan ambos. Casi todos los demás endpoints de vídeo de hiapi usan estos dos campos; q3-ad no. El tamaño solo se controla con size.
  • reference_audio_urls también se rechaza. A diferencia del nivel reference-to-video de Vidu Q4 (que puede recibir fragmentos de voz), q3-ad no tiene entrada de audio; su banda sonora se genera de forma nativa. (Cómo lo confirmé, en la sección de verificación más abajo.)

Cómo elegir size

size es una cadena width*height de un conjunto fijo. Puedes leerlo como una cuadrícula de cinco relaciones de aspecto × dos niveles de calidad:

Relación de aspecto Nivel 720 Nivel 1080
16:9 horizontal 1280*720 1920*1080
9:16 vertical 720*1280 1080*1920
1:1 cuadrado 1280*1280 1808*1808
4:3 1280*960 1920*1440
3:4 960*1280 1440*1920

Elige la fila según dónde se vaya a publicar (vertical 720*1280 / 1080*1920 para Reels y TikTok, fila horizontal para anuncios in-stream de YouTube) y la columna según el equilibrio entre calidad y presupuesto. La facturación se calcula por segundos de salida y el nivel determina el precio por segundo; consulta precios para más detalles; la facturación mínima es un clip de 3 segundos.

Solicitud mínima (curl)

Dos pasos: enviar y luego hacer polling.

# 1) Enviar: devuelve un taskId
curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-your-key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "vidu-q3-ad/reference-to-video",
    "input": {
      "prompt": "Un anuncio del smartwatch de titanio de la imagen de referencia 1: gira lentamente sobre un pedestal oscuro y reflectante mientras una franja de luz dura barre la caja cepillada de izquierda a derecha, la pantalla permanece apagada, la cámara mantiene un ángulo de héroe bajo. Iluminación comercial, alto contraste, detalle macro.",
      "reference_image_urls": [
        "https://your-cdn.com/watch-front.png",
        "https://your-cdn.com/watch-side.png"
      ],
      "duration": 5,
      "size": "1280*720"
    }
  }'
# -> {"data":{"taskId":"..."}}

# 2) Hacer polling hasta que data.status sea "success" o "fail"
curl -s https://api.hiapi.ai/v1/tasks/THE_TASK_ID \
  -H "Authorization: Bearer sk-your-key"
# Éxito -> data.status == "success", y data.output[0].url es el MP4

Las URL de las imágenes de referencia deben ser de acceso público: la tarea las descarga en el servidor, así que localhost o URL firmadas que caducan a los pocos segundos no sirven.

Python: enviar, hacer polling, descargar

import time
import requests  # o usa urllib/subprocess para mantener cero dependencias

API = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": "Bearer sk-your-key", "Content-Type": "application/json"}

def submit():
    payload = {
        "model": "vidu-q3-ad/reference-to-video",
        "input": {
            "prompt": "Toma publicitaria de la zapatilla de la imagen de referencia 1 girando sobre un plato giratorio iluminado, "
                      "acercamiento lento, luz de contorno suave, encuadre de héroe de producto.",
            "reference_image_urls": ["https://your-cdn.com/sneaker.png"],
            "duration": 5,
            "size": "1080*1920",   # vertical 1080p, para Reels / TikTok
        },
    }
    body = requests.post(API, headers=HEADERS, json=payload, timeout=60).json()
    task_id = (body.get("data") or {}).get("taskId")
    if not task_id:
        raise RuntimeError(f"envío rechazado: {body}")   # un 400 acaba aquí
    return task_id

def wait(task_id, timeout_s=600, interval=5):
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        data = requests.get(f"{API}/{task_id}", headers=HEADERS, timeout=30).json().get("data") or {}
        status = data.get("status")
        if status == "success":
            return data["output"][0]["url"]
        if status == "fail":
            raise RuntimeError(f"tarea fallida: {data.get('error')}")
        time.sleep(interval)
    raise TimeoutError(task_id)

url = wait(submit())
mp4 = requests.get(url, timeout=120).content   # descarga de inmediato: la URL de salida caduca
open("ad.mp4", "wb").write(mp4)

El renderizado de Q3-ad suele completarse en uno o dos minutos; un intervalo de polling de 5 segundos es más que suficiente. La URL de salida es temporal (lleva expireAt), así que descarga los bytes en cuanto la tarea tenga éxito, en lugar de enlazarla en caliente en una página.

Patrón para producción

Usa callbacks en lugar de polling. En un escenario de servidor, configura un callback de nivel superior para no tener que mantener un bucle de polling:

{
  "model": "vidu-q3-ad/reference-to-video",
  "input": { "prompt": "...", "reference_image_urls": ["..."], "duration": 5 },
  "callback": { "url": "https://your-app.com/hooks/hiapi", "when": "final" }
}

when solo admite "final": el hook se dispara una vez cuando la tarea entra en estado terminal y su cuerpo de solicitud es el mismo payload que devuelve GET /v1/tasks/{taskId}. El hook puede reintentarse, así que deduplica por taskId y mantén un polling lento como red de seguridad por si se pierde alguna entrega.

Bifurca según el cuerpo de la respuesta, no según el código de estado HTTP. Un fallo de renderizado sigue devolviendo HTTP 200; la información del fallo está en data.status == "fail" y los detalles en data.error. Solo los rechazos a nivel de solicitud (campos incorrectos, clave ausente, no autenticado) usan 4xx y devuelven {"code":400,"error_code":"INVALID_REQUEST","message":"...","data":null}.

Idempotencia. Cada envío crea una nueva tarea facturable, así que guarda el taskId en el instante en que lo recibas y, al reintentar, vuelve a hacer polling de ese ID. Nunca reenvíes por que una sola solicitud de polling agote el tiempo de espera.

Qué verifiqué

Sondeé el endpoint en vivo con valores no válidos, que se rechazan gratis en la fase de validación: no se crea ninguna tarea y no se factura nada (los valores no válidos fallan antes de que empiece cualquier renderizado, lo que es más seguro que omitir un campo). Las respuestas reales son el esquema:

  • Cuerpo de solicitud casi vacío → reference_image_urls: missing required field ...; duration: missing required field .... Por tanto, prompt, reference_image_urls y duration son obligatorios.
  • duration: 99999 → maximum: got 99,999, want 15; duration: 1 → minimum: got 1, want 3; duration: 4.5 → got number, want integer. Duration es un entero de 3 a 15.
  • reference_image_urls con 8 elementos → maxItems: got 8, want 7. El máximo es 7.
  • size: "9000p" → value must be one of '1280*720', '1280*960', '1280*1280', '960*1280', '720*1280', '1920*1080', '1920*1440', '1808*1808', '1440*1920', '1080*1920'.
  • aspect_ratio y resolution → ambos devuelven additional properties ... not allowed. No son campos de q3-ad; usa size.
  • reference_audio_urls enviado como un array con formato correcto → additional properties 'reference_audio_urls' not allowed. (Si se envía como algo que no es un array, primero se dispara una comprobación de medios aparte: "must be an array", lo cual es engañoso: el campo realmente no está en el esquema). Este nivel no tiene entrada de referencia de voz; el audio se genera de forma nativa.

Esta es una referencia de integración, así que no incluye afirmaciones sobre la calidad de imagen: no ejecuté ningún renderizado de pago para este artículo. Sobre cómo es un output[0].url exitoso, la página del modelo más abajo aloja el ejemplo propio de este endpoint: un MP4 real con pista de audio.

Guías relacionadas

Preguntas frecuentes

¿Cómo hago que el clip sea vertical u horizontal? Con size, no con aspect_ratio. Para vertical usa 720*1280 o 1080*1920; para horizontal, 1280*720 o 1920*1080.

¿Puedo aportar una voz en off o una referencia de sonido? En este nivel no: reference_audio_urls se rechaza. Q3-ad genera su propio audio nativo. Si necesitas usar un fragmento de referencia para guiar el audio, usa el nivel reference-to-video de Vidu Q4.

¿Cuántas imágenes de producto puedo enviar? De una a siete, en reference_image_urls, y referéncialas por número en el prompt ("la botella de la imagen de referencia 2").

Mi tarea devolvió 200 pero no hay vídeo, ¿por qué? Mira data.status. "fail" devuelve HTTP 200 y el motivo está en data.error; solo las solicitudes mal formadas reciben 4xx.

¿Cuánto cuesta? Se factura por segundos de salida y el nivel de size (nivel 720 vs nivel 1080) determina el precio por segundo. El mínimo es un clip de 3 segundos. Consulta las tarifas actuales en precios.

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