HiAPI
Blog

Tutorial

API de imagen a video de PixVerse V6: ejemplos de curl y Python listos para ejecutar

Oct 20, 2026
API de imagen a video de PixVerse V6: ejemplos de curl y Python listos para ejecutar
Modelo
pixverse-v6

Animar una imagen estática con pixverse-v6/image-to-video en hiapi requiere una sola llamada POST /v1/tasks más un sondeo: exactamente el mismo patrón de tareas asíncronas que usan todos los modelos generativos de la plataforma. Se diferencia del endpoint de texto a video en la entrada: proporcionas un fotograma inicial (image_url) junto con un prompt que describe cómo debe moverse. Esta guía ofrece ejemplos de curl y Python listos para copiar y pegar, los campos de entrada que este endpoint valida realmente y una sorpresa que hace tropezar a quienes vienen del texto a video.

Qué necesitas

  • Una clave de API de hiapi. Créala en la consola y envíala mediante Authorization: Bearer sk-<your-key>. Consulta la documentación de autenticación para el formato de la cabecera.
  • El ID de modelo pixverse-v6/image-to-video (conserva la barra: forma parte del ID, no es una ruta de URL). Es un ID de modelo distinto de pixverse-v6/text-to-video.
  • Una URL de imagen de acceso público para el primer fotograma. La plataforma obtiene esa imagen en el servidor, así que los enlaces a localhost o a buckets privados no funcionarán: aloja el primer fotograma en una ubicación accesible por GET para la API.
  • Con curl no hace falta instalar nada. Para el ejemplo de Python solo necesitas la biblioteca estándar y requests.

PixVerse V6 imagen a video toma la imagen estática que proporcionas como fotograma inicial y la anima en un clip de 1 a 15 segundos. Funciona a través de la API unificada de tareas asíncronas: envías la tarea, sondeas el resultado y luego descargas la URL de salida antes de que caduque.

Solicitud mínima funcional (curl)

Envía la tarea a POST /v1/tasks. Tanto image_url como prompt son obligatorios:

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-<your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "pixverse-v6/image-to-video",
    "input": {
      "image_url": "https://cdn.example.com/your-first-frame.jpg",
      "prompt": "Slow push-in; the subject turns toward the camera and smiles, soft breeze in the hair",
      "duration": 5,
      "resolution": "720p"
    }
  }'

La respuesta devuelve un ID de tarea:

{ "code": 200, "data": { "taskId": "a1b2c3d4-...." } }

Luego sondea GET /v1/tasks/<taskId> hasta que data.status sea success:

curl -s https://api.hiapi.ai/v1/tasks/a1b2c3d4-.... \
  -H "Authorization: Bearer sk-<your-key>"

En caso de éxito, la URL del video terminado está en data.output[0].url:

{
  "code": 200,
  "data": {
    "status": "success",
    "output": [{ "url": "https://temp.hiapi.ai/....mp4", "expireAt": 1760000000 }]
  }
}

Esa URL de salida es temporal: incluye una marca de tiempo expireAt. Descarga el .mp4 de inmediato y guárdalo en tu propio bucket; no lo enlaces directamente en caliente.

El mismo flujo en Python

import time, requests

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

def create_task():
    payload = {
        "model": "pixverse-v6/image-to-video",
        "input": {
            "image_url": "https://cdn.example.com/your-first-frame.jpg",
            "prompt": "Slow push-in; the subject turns toward the camera and smiles",
            "duration": 5,
            "resolution": "720p",
        },
    }
    r = requests.post(BASE, json=payload, headers=HEADERS, timeout=60)
    r.raise_for_status()
    data = r.json()
    task_id = (data.get("data") or {}).get("taskId")
    if not task_id:
        raise RuntimeError(f"create failed: {data}")  # los 400 repiten el campo incorrecto exacto
    return task_id

def wait_for(task_id, timeout_s=600):
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        try:
            r = requests.get(f"{BASE}/{task_id}", headers=HEADERS, timeout=30)
            task = (r.json().get("data") or {})
        except requests.RequestException:
            time.sleep(5)          # un fallo de red transitorio no debe abortar todo el sondeo
            continue
        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(5)              # aún pendiente: conserva el MISMO task_id y vuelve a sondear
    raise TimeoutError(task_id)

task_id = create_task()
url = wait_for(task_id)
video = requests.get(url, timeout=120).content
open("out.mp4", "wb").write(video)
print("saved out.mp4")

El bucle de sondeo comprueba explícitamente success y fail, y sigue esperando ante cualquier otro estado, por lo que maneja con robustez cualquier etiqueta de estado intermedio que la tarea reporte durante el renderizado. Si una solicitud de sondeo falla, duerme y vuelve a sondear el mismo task_id guardado en lugar de reenviar: reenviar genera un segundo costo de renderizado.

Parámetros que pixverse-v6/image-to-video valida realmente

Estos son los campos que el endpoint en producción acepta realmente. Los confirmé uno por uno enviando valores no válidos y leyendo los errores de validación, y luego los contrasté con la documentación del modelo:

Campo Tipo Valores / rango Descripción
image_url string Una URL de imagen pública Obligatorio. Primer fotograma. Una sola cadena de URL, no un array.
prompt string — Obligatorio. Describe el movimiento aplicado a ese fotograma.
duration integer 1–15, predeterminado 5 En segundos. Debe ser un entero, no una cadena.
resolution string 360p, 540p, 720p, 1080p Resolución de salida.
negative_prompt string — Opcional; contenido que hay que evitar.
seed integer — Opcional; reutilízalo para obtener resultados reproducibles.
style string anime, 3d_animation, clay, comic, cyberpunk Estilo predefinido opcional.
generate_audio boolean true / false, predeterminado false Opcional; activa o desactiva la pista de audio/ambiente generada.

Aquí no hay campo aspect_ratio. Esta es la única diferencia real con las solicitudes de texto a video que quizá hayas visto, y conviene tenerla presente (ver más abajo).

Consideraciones para producción

  • No hay aspect_ratio: la geometría de salida sigue a tu imagen de entrada. El endpoint de texto a video acepta una enumeración aspect_ratio; este endpoint la rechaza. Como proporcionas el primer fotograma, el clip hereda las dimensiones de ese fotograma. Si necesitas un clip vertical 9:16, recorta o rellena image_url a 9:16 antes de enviar: no hay ningún control de proporción que lo sobrescriba.
  • La estructura de la solicitud es cerrada. Cualquier campo fuera de la tabla anterior se rechaza con el error invalid input: <root>: additional properties '<field>' not allowed. Los campos mal escritos o inventados fallan ruidosamente al enviar, en lugar de ignorarse en silencio. Campos como first_frame, image, input_image, motion_mode y camera_movement se rechazan: la imagen inicial va en image_url y el movimiento de cámara se escribe en el texto de prompt.
  • duration es un entero, a diferencia de otros modelos de video de la plataforma que aceptan cadenas ("6"). Enviar "5" devuelve invalid input: duration: got string, want integer. Los valores fuera de rango reportan directamente el límite, por ejemplo duration: maximum: got 999, want 15.
  • Los errores se devuelven de un campo a la vez, con un sobre plano: {"code":400,"error_code":"INVALID_REQUEST","message":"invalid input: image_url: missing required field \"image_url\""}. Corrige el campo reportado y vuelve a enviar; el siguiente campo no válido (si lo hay) aparecerá en la siguiente llamada.
  • Una tarea fallida sigue devolviendo HTTP 200. La información del fallo está en el cuerpo de la respuesta, como data.status: "fail": comprueba el campo de estado, no solo el código de estado HTTP. Una URL de imagen que la plataforma no puede obtener es una causa habitual, así que confirma primero que tu primer fotograma es público.
  • La facturación es por segundo y crece con la resolución y la duración, por lo que un clip de 1080p/15s cuesta mucho más que uno de 540p/3s. Consulta las tarifas actuales en la página de precios antes de generar en lote.

Qué verifiqué (y qué no)

Deduje el contrato de entrada enviando valores deliberadamente no válidos al endpoint /v1/tasks en producción de pixverse-v6/image-to-video y leyendo las respuestas de validación de la propia API: una sonda de esquema de solo lectura, sin callback ni costo de renderizado. Hallazgos concretos que conviene recordar:

  • image_url es el campo obligatorio del primer fotograma y es una sola cadena, no un array. Enviar un número devuelve image_url: got number, want string; omitirlo devuelve missing required field "image_url". Los campos alternativos que podrías suponer (first_frame, image, input_image) se rechazan como propiedades desconocidas.
  • Este endpoint rechaza aspect_ratio (additional properties 'aspect_ratio' not allowed), aunque sea válido en pixverse-v6/text-to-video. Esta es la razón concreta por la que una solicitud de imagen a video se ve distinta de una de texto a video: la forma de la salida la determina tu fotograma, no un parámetro.
  • generate_audio es un verdadero interruptor booleano (predeterminado false): enviar una cadena devuelve generate_audio: got string, want boolean. Si quieres audio, ponlo en true; no se fuerza a activado ni a desactivado.

En esta sonda no rendericé ningún clip, así que esta guía no afirma nada sobre la calidad del movimiento ni sobre cuánto respeta el modelo el fotograma de entrada: solo documenta el contrato de solicitud exacto que aplica este endpoint.

Contenido relacionado

Preguntas frecuentes

¿Por qué la imagen a video no tiene aspect_ratio? Porque el clip comienza con tu fotograma de image_url y hereda sus dimensiones. Para controlar la proporción de salida, cambia el tamaño o recorta la imagen de entrada antes de enviar: el endpoint rechaza aspect_ratio como campo desconocido.

¿image_url acepta varias imágenes? No. En este endpoint es una sola cadena de URL; pasar un array provoca un fallo de validación. Solo establece el fotograma inicial.

¿duration se mide en segundos o en fotogramas? En segundos: cualquier entero de 1 a 15, predeterminado 5. Debe ser entero; la cadena "5" se rechaza.

¿En qué se diferencia del endpoint de texto a video? pixverse-v6/image-to-video anima el fotograma que proporcionas (image_url + prompt, sin aspect_ratio). pixverse-v6/text-to-video es un ID de modelo aparte, genera solo a partir del prompt y sí acepta aspect_ratio. Usa imagen a video cuando ya tengas la imagen fija inicial exacta que quieres animar.

¿Por qué falla mi solicitud con "additional properties not allowed"? Enviaste un campo que no existe en el esquema. La entrada solo acepta image_url, prompt, duration, resolution, negative_prompt, seed, style y generate_audio.

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