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
| Parametro | Tipo | Note |
|---|---|---|
model | stringa (obbligatorio) | Uno slug di un modello video di /v1/models. |
prompt | stringa (obbligatorio) | Descrizione testuale della scena. |
image_url | string | Immagine sorgente singola per la generazione da immagine a video. URL o URI data:. |
image_urls | string[] | Immagini sorgente multiple (Veo). Ha la precedenza su image_url. |
image_mode | string | Veo: «frame» (predefinito — primo/ultimo fotogramma) o «reference» (riferimenti di stile). |
duration | int | Durata 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_ratio | string | ad es. «16:9», «9:16», «1:1». |
resolution | string | Se il modello offre diversi livelli, ad es. «720p», «1080p». |
input | object | Opzione 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
| Slug | Provider | Note |
|---|---|---|
veo-3 | Veo 3.1 Fast — più rapido ed economico di Quality, con audio nativo. | |
veo-3-quality | Veo 3.1 Quality — massima fedeltà. | |
veo-3-lite | Veo 3.1 Lite — livello economico per le anteprime. | |
seedance-2-5 | ByteDance | Seedance 2.5 — clip fino a 30s, riferimenti multimodali, fatturazione al secondo. |
seedance-2 | ByteDance | Seedance 2 — coerenza tra più inquadrature, fino a 1080p, fatturazione al secondo. |
seedance-2-fast | ByteDance | Seedance 2 Fast — livello economico, fino a 720p, fatturazione al secondo. |
seedance-2-mini | ByteDance | Seedance 2.0 Mini — livello più economico, solo 480p/720p, per bozze e produzioni in volume. |
wan-2-7 | Alibaba | Wan 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
| Campo | Tipo | Note |
|---|---|---|
id | string | ID dell’attività preceduto da vid_-. Stabile anche quando cambia il provider upstream. |
object | string | "video". |
status | string | queued | in_progress | completed | failed. |
model | string | Lo slug del modello inviato. |
created_at | int | Secondi Unix in cui abbiamo accettato l’invio. |
completed_at | int | null | Secondi Unix del termine dell’attività; null mentre è in corso. |
expires_at | int | Secondi Unix, 30 giorni dopo l’invio: l’attività può essere interrogata almeno fino ad allora. Gli URL dei suoi risultati non scadono. |
progress | int | Da 0 a 100. Attualmente restituiamo 0 fino al completamento, poi 100. |
output | object | null | {url, urls[], duration_ |
error | object | 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/videosrestituisce 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/ | Asincrono /v1/videos | |
|---|---|---|
| Ideale per | Script e notebook occasionali | Produzione, dispositivi mobili, attività di lunga durata |
| Rete | Mantiene aperta la connessione fino a 9 min | Invia in pochi secondi; controlla lo stato secondo i tuoi tempi |
| Recupero dagli errori | Perdere la risposta = perdere l’attività | Esegui di nuovo la query sull’ID in qualsiasi momento prima di expires_at |
| Concorrenza | Un worker server per chiamata | Invia e rilascia immediatamente la connessione |
| Struttura della risposta | Struttura di OpenAI /images/ | Struttura 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.