Documentación

Documentación

Generación de vídeo

Generación de vídeo a partir de texto e imágenes con la familia Veo 3.1 de Google. Dos modalidades de API: un endpoint síncrono de una sola llamada que espera hasta que el vídeo esté listo, y un par asíncrono de envío y consulta de estado para cargas de trabajo de producción.

Endpoints: POST /v1/video/generations (síncrono, explicado primero) y POST /v1/videos + GET /v1/videos/{id} (asíncrono, recomendado para producción). La generación tarda de 30 segundos a varios minutos, según el modelo y la duración; para el endpoint síncrono, establece un tiempo de espera generoso en el cliente (600s es un valor seguro).

Generación de vídeo a partir de texto

Pasa un prompt y elige un modelo. aspect_ratio es opcional.

import requests

resp = requests.post(
    "https://api.kunavo.com/v1/video/generations",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "veo-3",
        "prompt": "a cinematic dolly-in on a red origami crane unfolding, soft light",
        "aspect_ratio": "16:9",
    },
    timeout=600,  # video generation can take minutes
)
print(resp.json()["data"][0]["url"])

duration (en segundos) define la duración del clip en los modelos con facturación por segundo: los modelos Seedance y el modelo Wan 2.7. Los modelos Veo lo ignoran; cada clip de Veo dura 8 segundos y se factura por vídeo.

# The models billed per second take a clip length: here 5 seconds of
# Seedance 2. Leave "duration" out and these models make a 5-second clip.
resp = requests.post(
    "https://api.kunavo.com/v1/video/generations",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "seedance-2",
        "prompt": "a paper crane unfolds into a flock of birds over a city at dawn",
        "duration": 5,
        "aspect_ratio": "16:9",
    },
    timeout=600,
)
print(resp.json()["data"][0]["url"])

Imagen a vídeo

Añade un image_url y Veo lo animará; de forma predeterminada, la imagen fija se convierte en el primer fotograma del vídeo.

# Image-to-video: animate a still image. The source image can be an
# https URL, or inline base64 — Kunavo hosts it for the model automatically.
resp = requests.post(
    "https://api.kunavo.com/v1/video/generations",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "veo-3",
        "prompt": "the crane gently lifts off the desk",
        "image_url": "https://your-cdn.com/source.png",
    },
    timeout=600,
)
print(resp.json()["data"][0]["url"])
image_url acepta una URL https:// o un URI base64 data: en línea. base64 se decodifica y se aloja automáticamente en la CDN de Kunavo antes de ejecutar el modelo.

Primer fotograma, último fotograma y referencias

image_mode controla cómo usa Veo las imágenes. Pasa image_urls (un array) para enviar más de una.

  • frame (predeterminado): una imagen sirve como primer fotograma; dos imágenes sirven como primer y último fotograma, y Veo genera la transición entre ellas.
  • reference: hasta tres imágenes sirven como referencias de estilo y tema. Orientan el aspecto, pero nunca aparecen como fotogramas.
# First + last frame: pass two images and Veo fills in the motion.
resp = requests.post(
    "https://api.kunavo.com/v1/video/generations",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "veo-3",
        "prompt": "a smooth transition between the two poses",
        "image_urls": [
            "https://your-cdn.com/first.png",
            "https://your-cdn.com/last.png",
        ],
        "image_mode": "frame",
    },
    timeout=600,
)

Subir un fotograma de origen

Si solo tienes un archivo local, súbelo una vez mediante POST /v1/files y reutiliza la URL permanente que se devuelve.

# No public URL? Upload the still first, then animate it.
with open("frame.png", "rb") as f:
    up = requests.post(
        "https://api.kunavo.com/v1/files",
        headers={"Authorization": f"Bearer {API_KEY}"},
        files={"file": f},
    ).json()

resp = requests.post(
    "https://api.kunavo.com/v1/video/generations",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "veo-3",
        "prompt": "slow push-in, volumetric light",
        "image_url": up["url"],
    },
    timeout=600,
)

Parámetros

ParámetroTipoNotas
modelcadena (obligatorio)Identificador de un modelo de vídeo de /v1/models.
promptcadena (obligatorio)Descripción textual de la toma.
image_urlstringImagen de origen única para generar vídeo a partir de imágenes. URL o URI data:.
image_urlsstring[]Varias imágenes de origen (Veo). Tiene prioridad sobre image_url.
image_modestringVeo: "frame" (predeterminado: primer/último fotograma) o "reference" (referencias de estilo).
durationintDuración del clip en segundos en los modelos con facturación por segundo, 5 cuando se omite: Seedance 2, Seedance 2 Fast y Seedance 2.0 Mini admiten 4–15, Seedance 2.5 admite 4–30 y Wan 2.7 admite 2–15. Una duración fuera del rango de un modelo se rechaza con un error 400. Veo ignora este valor: cada clip de Veo dura 8 segundos.
aspect_ratiostringPor ejemplo, "16:9", "9:16", "1:1".
resolutionstringEn los modelos que ofrecen distintos niveles, por ejemplo, "720p", "1080p".
inputobjectMecanismo alternativo; consulte más abajo Raw input.

Mecanismo alternativo para entrada sin procesar

En los modelos con facturación por segundo, input se envía tal cual al proveedor ascendente, en lugar de la entrada que crearía nuestro adaptador. Los modelos Veo lo ignoran.

Los campos que determinan el precio son la excepción: resolution siempre se envía como uno de los niveles del modelo (el nivel predeterminado si se omite o no corresponde a ninguno de ellos), y duration debe ser un número entero de segundos dentro del rango del modelo (cualquier otro valor se rechaza con un error 400; se envía 5 si se omite). No se leen resolution ni duration de la propia solicitud cuando se establece input.

Modelos de vídeo

SlugProveedorNotas
veo-3GoogleVeo 3.1 Fast: más rápido y económico que Quality, con audio nativo.
veo-3-qualityGoogleVeo 3.1 Quality: máxima fidelidad.
veo-3-liteGoogleVeo 3.1 Lite: nivel económico para vistas previas.
seedance-2-5ByteDanceSeedance 2.5: clips de hasta 30s, referencias multimodales y facturación por segundo.
seedance-2ByteDanceSeedance 2: coherencia entre varias tomas, hasta 1080p y facturación por segundo.
seedance-2-fastByteDanceSeedance 2 Fast: nivel económico, hasta 720p y facturación por segundo.
seedance-2-miniByteDanceSeedance 2.0 Mini: nivel más económico, solo 480p/720p, para borradores y generación en volumen.
wan-2-7AlibabaWan 2.7: generación cinematográfica de vídeo a partir de texto, con facturación por segundo.

Consulta siempre /models para ver la lista actualizada y los precios por modelo.

Latencia y tiempos de espera

  • Kunavo consulta la tarea al proveedor ascendente durante un máximo de 540 segundos antes de devolver un 504.
  • Niveles rápidos (Veo 3.1 Fast / Lite): a menudo, 30–90s.
  • Niveles de calidad y duraciones más largas: 2–5 minutos.
  • Establece el tiempo de espera de tu cliente HTTP en al menos 600s.
  • En redes inestables o clientes móviles, prefiere la API asíncrona: el envío tarda unos segundos y luego consultas el resultado.

API asíncrona (enviar + consultar)

Endpoints: POST /v1/videos + GET /v1/videos/{id}. El objeto de tarea que devuelven utiliza los nombres de campo y los valores de estado del objeto de video de OpenAI, y añade output con la URL del clip. La solicitud es un cuerpo JSON con los parámetros anteriores.

El endpoint síncrono anterior mantiene abierta una conexión HTTP durante un máximo de nueve minutos; resulta práctico para scripts de una sola llamada, pero no es fiable en dispositivos móviles y consume recursos del servidor de forma ineficiente. El par asíncrono devuelve de inmediato un identificador vid_*; consulta GET /v1/videos/{id} hasta que status tenga el valor completed o failed. Los modelos, los precios y las URL archivadas son los mismos.

import requests, time

# 1. Submit — returns immediately with a task id. No long-lived connection.
submit = requests.post(
    "https://api.kunavo.com/v1/videos",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        # Optional: retrying with the same key inside ~24h returns the
        # original task rather than submitting again.
        "Idempotency-Key": "my-task-uuid",
    },
    json={
        "model": "veo-3",
        "prompt": "a cinematic dolly-in on a red origami crane",
        "aspect_ratio": "16:9",
    },
    timeout=60,
).json()

task_id = submit["id"]              # "vid_abc..."
status  = submit["status"]          # "queued"

# 2. Poll until terminal. Recommended cadence: 5s, backing off to 30s.
while status not in ("completed", "failed"):
    time.sleep(5)
    r = requests.get(
        f"https://api.kunavo.com/v1/videos/{task_id}",
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=30,
    ).json()
    status = r["status"]

if status == "failed":
    raise RuntimeError(r["error"]["message"])
print(r["output"]["url"])

Desde el SDK de OpenAI, envía ese cuerpo con client.post y consulta el estado con client.get, como se muestra a continuación. El método client.videos.create propio del SDK no funciona aquí: solo acepta los parámetros de Sora y los envía como un formulario multipart (en el SDK de Python desde la versión 2.27.0), que este endpoint rechaza con un error 400.

import time
from openai import OpenAI

client = OpenAI(api_key=API_KEY, base_url="https://api.kunavo.com/v1")

# The same JSON body, sent with the client's own post().
task = client.post(
    "/videos",
    body={
        "model": "veo-3",
        "prompt": "a cinematic dolly-in on a red origami crane",
        "aspect_ratio": "16:9",
    },
    # object hands back the JSON as parsed. Passing dict instead raises
    # after the task has been created.
    cast_to=object,
    options={"headers": {"Idempotency-Key": "my-task-uuid"}},
)

while task["status"] not in ("completed", "failed"):
    time.sleep(5)
    task = client.get(f"/videos/{task['id']}", cast_to=object)

if task["status"] == "failed":
    raise RuntimeError(task["error"]["message"])
print(task["output"]["url"])

Formato de la respuesta

CampoTipoNotas
idstringIdentificador de tarea con prefijo vid_. Se mantiene estable aunque cambie el proveedor ascendente.
objectstring"video".
statusstringen cola | en curso | completada | fallida.
modelstringEl slug del modelo que enviaste.
created_atintSegundos Unix en que aceptamos la solicitud.
completed_atint | nullSegundos Unix en que la tarea llegó a un estado terminal; null mientras está pendiente.
expires_atintSegundos Unix, 30 días después del envío: la tarea se puede consultar al menos hasta entonces. Las URL de resultados de la tarea no caducan.
progressint0–100. Actualmente indicamos 0 hasta que la tarea alcanza un estado terminal; entonces indicamos 100.
outputobject | null{url, urls[], duration_seconds, resolution} al completarse. duration_seconds es la duración del clip solicitada al modelo, null en Veo; resolution es el nivel de resolución del clip, o null si no se registró ninguno.
errorobject | null{code, message} si falla.

Encabezados y convenciones

  • Idempotency-Key (opcional, hasta 128 caracteres): si se envía dos veces la misma clave en la misma cuenta, se devuelve la tarea original en vez de crear un duplicado. Útil para reintentar solicitudes.
  • POST /v1/videos devuelve 202 Accepted en un envío nuevo y 200 OK cuando una clave de idempotencia permite recuperar un resultado anterior.
  • GET /v1/videos/{id} está limitado al propietario: consultar una tarea de otra cuenta devuelve 404. Nunca confirmamos que exista un identificador que pertenezca a otra persona.
  • Frecuencia de consulta recomendada: cada 5 s, con retroceso exponencial hasta 30 s. Las tareas suelen completarse en 30 s–5 min, según el modelo y la resolución.

Cuándo usar cada uno

Síncrono /v1/video/generationsAsíncrono /v1/videos
Ideal paraScripts puntuales y notebooksProducción, móviles y tareas de larga duración
RedMantiene la conexión abierta hasta 9 minEnvía en segundos; consulta según tu propio calendario
Recuperación tras fallosPerder la respuesta = perder la tareaVuelve a consultar el identificador en cualquier momento antes de expires_at
ConcurrenciaUn trabajador del servidor por llamadaEl envío libera recursos de inmediato
Formato de la respuestaEstructura de OpenAI /images/generationsEstructura de OpenAI /videos

Respuesta y almacenamiento

La respuesta tiene el formato estándar de OpenAI: data[].url. La URL es un enlace permanente a files.kunavo.com: Kunavo archiva cada resultado en su propia CDN, así que, a diferencia de las URL directas del proveedor, nunca caduca. Todos los clips también aparecen en /app/assets.

Próximos pasos