Documentazione

Documentazione

Generazione video

Generazione di video da testo e da immagini con la famiglia Veo 3.1 di Google. Sono disponibili due modalità API: un endpoint sincrono in una sola chiamata, che attende fino a quando il clip è pronto, e una coppia di endpoint asincroni di invio e interrogazione dello stato, pensata per i carichi di lavoro di produzione.

Endpoint: POST /v1/video/generations (sincrono, descritto per primo) e POST /v1/videos + GET /v1/videos/{id} (asincroni, consigliati per la produzione). La generazione richiede da 30 secondi a diversi minuti, a seconda del modello e della durata. Per l’endpoint sincrono, imposta un timeout client ampio (600s è un valore sicuro).

Da testo a video

Passa un prompt e seleziona un modello. aspect_ratio è facoltativo.

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 (in secondi) imposta la durata del clip nei modelli con fatturazione al secondo: i modelli Seedance e il modello Wan 2.7. I modelli Veo lo ignorano; ogni clip Veo dura 8 secondi e viene fatturato per video.

# 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"])

Da immagine a video

Aggiungi un image_url e Veo lo animerà: per impostazione predefinita, l’immagine statica diventa il primo fotogramma del video.

# 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 accetta un URL https:// o una URI base64 data: incorporata. I dati base64 vengono decodificati e ospitati automaticamente sulla CDN di Kunavo prima dell’esecuzione del modello.

Primo fotogramma, ultimo fotogramma e riferimenti

image_mode controlla il modo in cui Veo usa le immagini. Passa image_urls (un array) per inviarne più di una.

  • frame (predefinito) — una sola immagine diventa il primo fotogramma; con due immagini, la prima e l’ultima diventano i fotogrammi iniziale e finale, e Veo genera la transizione tra loro.
  • reference — fino a tre immagini fungono da riferimenti per stile e soggetto. Guidano l’aspetto, ma non compaiono mai come fotogrammi.
# 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,
)

Caricamento di un fotogramma sorgente

Se hai solo un file locale, caricalo una volta tramite POST /v1/files e riutilizza l’URL permanente restituito.

# 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,
)

Parametri

ParametroTipoNote
modelstringa (obbligatorio)Uno slug di un modello video di /v1/models.
promptstringa (obbligatorio)Descrizione testuale della scena.
image_urlstringImmagine sorgente singola per la generazione da immagine a video. URL o URI data:.
image_urlsstring[]Immagini sorgente multiple (Veo). Ha la precedenza su image_url.
image_modestringVeo: «frame» (predefinito — primo/ultimo fotogramma) o «reference» (riferimenti di stile).
durationintDurata del clip in secondi nei modelli con fatturazione al secondo, 5 se omessa: Seedance 2, Seedance 2 Fast e Seedance 2.0 Mini accettano 4–15, Seedance 2.5 accetta 4–30, Wan 2.7 accetta 2–15. Una durata fuori dall’intervallo di un modello viene rifiutata con un errore 400. Veo ignora questo valore: ogni clip Veo dura 8 secondi.
aspect_ratiostringad es. «16:9», «9:16», «1:1».
resolutionstringSe il modello offre diversi livelli, ad es. «720p», «1080p».
inputobjectOpzione di ripiego: vedi Raw input qui sotto.

Opzione di ripiego per input grezzo

Nei modelli con fatturazione al secondo, input viene inviato al servizio upstream così com’è, al posto dell’input che il nostro adattatore genererebbe. I modelli Veo lo ignorano.

Fanno eccezione i campi che determinano il prezzo: resolution viene sempre inviato come uno dei livelli del modello (il livello predefinito se il campo è omesso o il valore non corrisponde a nessuno di essi) e duration deve essere un numero intero di secondi compreso nell’intervallo del modello (qualsiasi altro valore viene rifiutato con un errore 400; se il campo è omesso viene inviato 5). I campi resolution e duration della richiesta stessa non vengono letti quando è impostato input.

Modelli video

SlugProviderNote
veo-3GoogleVeo 3.1 Fast — più rapido ed economico di Quality, con audio nativo.
veo-3-qualityGoogleVeo 3.1 Quality — massima fedeltà.
veo-3-liteGoogleVeo 3.1 Lite — livello economico per le anteprime.
seedance-2-5ByteDanceSeedance 2.5 — clip fino a 30s, riferimenti multimodali, fatturazione al secondo.
seedance-2ByteDanceSeedance 2 — coerenza tra più inquadrature, fino a 1080p, fatturazione al secondo.
seedance-2-fastByteDanceSeedance 2 Fast — livello economico, fino a 720p, fatturazione al secondo.
seedance-2-miniByteDanceSeedance 2.0 Mini — livello più economico, solo 480p/720p, per bozze e produzioni in volume.
wan-2-7AlibabaWan 2.7 — generazione cinematografica di video da testo, fatturazione al secondo.

Controlla sempre /models per l’elenco aggiornato e i prezzi per modello.

Latenza e timeout

  • Kunavo interroga l’attività upstream per un massimo di 540 secondi prima di restituire un 504.
  • Livelli Fast (Veo 3.1 Fast / Lite): spesso 30–90s.
  • Livelli Quality e durate maggiori: 2–5 minuti.
  • Imposta il timeout del client HTTP ad almeno 600s.
  • Su reti instabili o client mobili, preferisci invece l’API asincrona: l’invio termina in pochi secondi e puoi interrogare lo stato per ottenere il risultato.

API asincrona (invio + polling)

Endpoint: POST /v1/videos + GET /v1/videos/{id}. L’oggetto task che restituiscono usa i nomi dei campi e i valori di stato dell’oggetto video di OpenAI, e aggiunge output con l’URL della clip. La richiesta è un corpo JSON con i parametri sopra indicati.

L’endpoint sincrono qui sopra mantiene aperta una connessione HTTP per un massimo di nove minuti: è comodo per gli script che effettuano una sola chiamata, ma inaffidabile sui dispositivi mobili e inefficiente lato server. La coppia asincrona restituisce subito un ID vid_*; interroga GET /v1/videos/{id} finché status non è completed o failed. Stessi modelli, stessi prezzi, stessi URL archiviati.

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"])

Con l’SDK OpenAI, invia quel corpo della richiesta con client.post e interroga lo stato con client.get, come indicato di seguito. Il metodo client.videos.create proprio dell’SDK non funziona in questo caso: accetta solo i parametri di Sora e (nell’SDK Python dalla versione 2.27.0) li invia come modulo multipart, che questo endpoint rifiuta con un errore 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"])

Struttura della risposta

CampoTipoNote
idstringID dell’attività preceduto da vid_-. Stabile anche quando cambia il provider upstream.
objectstring"video".
statusstringqueued | in_progress | completed | failed.
modelstringLo slug del modello inviato.
created_atintSecondi Unix in cui abbiamo accettato l’invio.
completed_atint | nullSecondi Unix del termine dell’attività; null mentre è in corso.
expires_atintSecondi Unix, 30 giorni dopo l’invio: l’attività può essere interrogata almeno fino ad allora. Gli URL dei suoi risultati non scadono.
progressintDa 0 a 100. Attualmente restituiamo 0 fino al completamento, poi 100.
outputobject | null{url, urls[], duration_seconds, resolution} al completamento. duration_seconds è la durata del clip richiesta al modello, null con Veo; resolution è il livello di risoluzione del clip, oppure null se non è stato registrato nessun livello.
errorobject | null{code, message} in caso di errore.

Intestazioni e convenzioni

  • Idempotency-Key (facoltativo, fino a 128 caratteri) — se invii due volte la stessa chiave per lo stesso account, viene restituita l’attività originale anziché crearne un duplicato. Utile per i cicli di nuovo tentativo.
  • POST /v1/videos restituisce 202 Accepted per un nuovo invio e 200 OK quando una chiave di idempotenza richiama un risultato precedente.
  • GET /v1/videos/{id} è limitato al proprietario: se interroghi un’attività appartenente a un altro account, viene restituito 404. Non confermiamo mai l’esistenza di un ID appartenente a qualcun altro.
  • Frequenza di interrogazione consigliata: ogni 5 s, con backoff esponenziale fino a 30 s. A seconda del modello e della risoluzione, le attività si completano di solito in 30 s – 5 min.

Quando usare quale

Sincrono /v1/video/generationsAsincrono /v1/videos
Ideale perScript e notebook occasionaliProduzione, dispositivi mobili, attività di lunga durata
ReteMantiene aperta la connessione fino a 9 minInvia in pochi secondi; controlla lo stato secondo i tuoi tempi
Recupero dagli erroriPerdere la risposta = perdere l’attivitàEsegui di nuovo la query sull’ID in qualsiasi momento prima di expires_at
ConcorrenzaUn worker server per chiamataInvia e rilascia immediatamente la connessione
Struttura della rispostaStruttura di OpenAI /images/generationsStruttura di OpenAI /videos

Risposta e archiviazione

La risposta ha la struttura standard di OpenAI: data[].url. L’URL è un link permanente files.kunavo.com: Kunavo archivia ogni risultato sulla propria CDN, quindi, a differenza degli URL upstream grezzi, non scade mai. Ogni clip appare anche in /app/assets.

Prossimi passi