Documentazione

Documentazione

Generazione di immagini

Due endpoint: da testo a immagine e modifica o trasformazione di immagini. Gli URL dei risultati restituiti da entrambi sono permanenti: Kunavo archivia ogni immagine sulla propria CDN.

Kunavo offre /v1/images/generations (da testo a immagine) e /v1/images/edits (da immagine a immagine o modifica). Per impostazione predefinita, entrambi funzionano in modo sincrono: la chiamata rimane in attesa finché l'elaborazione upstream non termina, quindi restituisce gli URL dei risultati. In alternativa, puoi usare la modalità asincrona con "async": true o un webhook_url.

Da testo a immagine

Endpoint: POST /v1/images/generations. Usa il metodo images.generate dell'SDK OpenAI oppure invia direttamente una richiesta curl.

img = client.images.generate(
    model="nano-banana-pro",
    prompt="a red origami crane on a white desk, soft window light",
    size="1024x1024",
)
print(img.data[0].url)

Parametri comuni

ParametroTipoNote
modelstringQualsiasi slug di modello per immagini da /v1/models.
promptstringPrompt testuale. Sempre obbligatorio.
sizestringFormato OpenAI, ad esempio "1024x1024". Convertito nel rapporto d'aspetto upstream.
qualitystandard|hdQuando supportato (gpt-image).
resolution1K|2K|4KLivello di risoluzione per i modelli a tariffe differenziate: vedi Resolution tiers qui sotto. Il costo dipende dal livello.
nintNumero di immagini. La maggior parte dei modelli ne restituisce sempre 1.
inputobjectOpzione di ripiego: vedi Raw input qui sotto.
Gli URL dei risultati sono link permanenti files.kunavo.com: Kunavo archivia ogni immagine sulla propria CDN, quindi, a differenza degli URL upstream grezzi, non scadono mai. Se un’immagine non può essere archiviata, il suo URL è quello temporaneo dell’upstream, su un altro host: scarica quell’immagine e ospitala nuovamente.

Livelli di risoluzione

Diversi modelli di immagini applicano prezzi a fasce in base alla risoluzione di output. Passa resolution per scegliere un livello; se lo ometti, viene usato quello predefinito. Il costo viene calcolato in base al livello effettivamente prodotto.

Modello (e relativa variante -edit)Livelli — prezzo per ciascunoPredefinito
nano-banana-21K $0.0469 · 2K $0.0707 · 4K $0.10571K
nano-banana-pro1K / 2K $0.067 · 4K $0.122K
gpt-image-21K / 2K / 4K — $0.0633 (fisso)1K

Anche la famiglia veo-3 prevede tariffe differenziate per risoluzione: i prezzi per ciascun livello sono indicati nella pagina /models di ogni modello.

GPT-Image-2 non può generare immagini in 4K con rapporto d'aspetto 1:1 (quadrato). Se imposti resolution su 4K con una dimensione quadrata, la richiesta viene automaticamente ridotta a 2K, con addebito corrispondente.

Modifica o trasformazione di immagini

Endpoint: POST /v1/images/edits. Per le modifiche a una singola immagine, passa l'immagine sorgente come URL (HTTPS o URI data: in base64) tramite image; per i riferimenti a più immagini usa image_urls.

# Image edit / image-to-image: pass the source image URL
import requests

resp = requests.post(
    "https://api.kunavo.com/v1/images/edits",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "gpt-image-2-edit",
        "prompt": "recolor the crane to bright blue, keep composition",
        "image": "https://your-cdn.com/source.png",
    },
)
print(resp.json()["data"][0]["url"])

Riferimenti a più immagini

Alcuni modelli (Nano Banana Edit, GPT-Image-2 Edit) accettano più immagini di riferimento.

# nano-banana-edit accepts multiple reference images
resp = requests.post(
    "https://api.kunavo.com/v1/images/edits",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "nano-banana-edit",
        "prompt": "merge subject from img1 with background from img2",
        "image_urls": [
            "https://your-cdn.com/subject.png",
            "https://your-cdn.com/background.png",
        ],
    },
)

Modelli di modifica supportati

  • nano-banana-edit — modifica con Google Nano Banana
  • gpt-image-2-edit — da immagine a immagine con OpenAI

Fornire un'immagine sorgente

La modifica delle immagini e la generazione di video da immagini richiedono entrambe un'immagine sorgente accessibile al modello. Hai tre possibilità: scegli quella più adatta alla tua applicazione.

  • Hai già un URL pubblico: passalo direttamente come image (oppure image_url per i video). Fatto.
  • Hai un file locale: caricalo una volta tramite POST /v1/files, quindi riutilizza l'URL permanente restituito.
  • Hai dati binari grezzi nel codice: includili in linea come URI data: in base64; Kunavo li ospita automaticamente.

Opzione A — carica tramite POST /v1/files

Invia multipart/form-data con un campo file (oppure JSON { "file": "data:..." }). Riceverai un URL files.kunavo.com permanente. Sono accettati file fino a 25 MB.

# Upload a local file → get a permanent Kunavo URL
import requests

with open("source.png", "rb") as f:
    up = requests.post(
        "https://api.kunavo.com/v1/files",
        headers={"Authorization": f"Bearer {API_KEY}"},
        files={"file": f},
    ).json()

# up["url"] is now a permanent https://files.kunavo.com/... URL
edit = requests.post(
    "https://api.kunavo.com/v1/images/edits",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "gpt-image-2-edit",
        "prompt": "recolor the crane to bright blue",
        "image": up["url"],
    },
).json()
print(edit["data"][0]["url"])

Opzione B — incorporamento in base64

Puoi saltare del tutto il caricamento separato: inserisci un URI data: nel campo image o image_url. Kunavo lo decodifica, lo ospita sulla propria CDN e passa al modello l'URL ospitato.

# Or skip the upload step — pass base64 inline, we host it for you.
import base64, requests

with open("source.png", "rb") as f:
    b64 = base64.b64encode(f.read()).decode()

resp = requests.post(
    "https://api.kunavo.com/v1/images/edits",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "gpt-image-2-edit",
        "prompt": "recolor the crane to bright blue",
        "image": f"data:image/png;base64,{b64}",
    },
)
Lo stesso vale per POST /v1/video/generations: image_url accetta un URL https o un URI data: in base64 per i modelli di generazione di video da immagini, come Veo.

Struttura della risposta

Entrambi gli endpoint restituiscono la risposta standard images di OpenAI:

{
  "created": 1779360636,
  "data": [
    { "url": "https://files.kunavo.com/image/.../result.png" }
  ]
}

Latenza e timeout

La generazione di immagini richiede in genere da 5 a 60 secondi, a seconda del modello. Nella modalità sincrona predefinita, Kunavo mantiene aperta la connessione e interroga il servizio upstream per un massimo di 540 secondi prima di restituire un errore 504. Imposta di conseguenza il timeout lato client oppure usa l'API asincrona per inviare la richiesta in pochi secondi e controllarne lo stato secondo i tuoi tempi:

  • Nano Banana / Nano Banana 2: in genere meno di 10s.
  • Nano Banana Pro / GPT-Image-2: 15–60s.
  • Riferimenti multipli + livello premium: fino a circa 2 minuti.

API asincrona (invio + polling o webhook)

Per impostazione predefinita, entrambi gli endpoint per le immagini sono sincroni: la chiamata mantiene aperta la connessione fino al completamento del rendering upstream, quindi restituisce la risposta standard images di OpenAI. Per processi in batch, livelli premium lenti o chiamate serverless che non possono mantenere una richiesta aperta a lungo, aggiungi "async": true (o un webhook_url) a /v1/images/generations o /v1/images/edits.

La risposta all'invio viene restituita subito con un ID img_*. Interroga GET /v1/images/{id} finché status non diventa completed o failed, oppure registra un webhook e salta del tutto il polling.

# Async: add "async": true (or a webhook_url) to either image endpoint.
# The call returns instantly with an img_ id instead of holding the connection.
import requests, time

API = "https://api.kunavo.com/v1"
H = {"Authorization": f"Bearer {API_KEY}"}

submit = requests.post(
    f"{API}/images/generations",
    headers=H,
    json={"model": "gpt-image-2", "prompt": "a red panda", "async": True},
).json()

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

# Poll GET /v1/images/{id} until terminal — or skip polling and use a webhook.
while status not in ("completed", "failed"):
    time.sleep(3)
    r = requests.get(f"{API}/images/{task_id}", headers=H).json()
    status = r["status"]

if status == "completed":
    print(r["output"]["url"])
else:
    print("failed:", r["error"])

Oggetto attività

CampoTipoNote
idstringID attività img_* — passalo a GET /v1/images/{id}.
statusstringqueued | in_progress | completed | failed.
outputobject|nullAl completamento: { url, urls, archived }. archived è false quando un’immagine non è stata archiviata e il suo URL è quello temporaneo dell’upstream.
errorobject|nullIn caso di errore: { code, message }.
expires_atint|nullSecondi Unix, 30 giorni dopo l’invio: l’attività può essere interrogata almeno fino ad allora. Gli URL dei suoi risultati non scadono.
GET /v1/images/{id} è limitato al proprietario: una richiesta con un ID non tuo restituisce 404, indistinguibile da un ID mai assegnato. Frequenza di polling consigliata: ogni 3 s, con backoff esponenziale fino a circa 30 s.

Webhook

Passa webhook_url (https) all'invio e Kunavo invierà via POST un evento firmato image.completed / image.failed al termine dell'attività: stesso formato e firma dei webhook per video e musica:

  • X-Kunavo-Webhook-Id — ID evento stabile, identico a ogni nuovo tentativo (usalo per deduplicare).
  • X-Kunavo-Webhook-Timestamp — secondi Unix in cui è stata calcolata la firma.
  • X-Kunavo-Webhook-Signature — sha256= HMAC-SHA256 su {timestamp}.{rawBody}, con la chiave segreta del tuo webhook.
Tentativi di consegna con backoff per circa 9 h. La riga asincrona resta interrogabile per tutto il periodo, quindi un webhook non ricevuto può sempre essere recuperato con GET.

Opzione di ripiego per input grezzo

Per i parametri specifici del modello che Kunavo non mappa (seed, prompt negativo, formato di output), passa input. Viene inviato al servizio upstream così com’è, al posto dell’input che il nostro adattatore genererebbe.

Fa eccezione il campo che seleziona il livello di prezzo di un modello: resolution, oppure rendering_speed per Ideogram. Per un modello con livelli, viene sempre inviato come uno dei livelli del modello e ti viene addebitato il costo di quel livello. Quando input lo omette o fornisce un valore che non corrisponde a nessuno dei livelli del modello, viene inviato e addebitato il livello predefinito. Lo stesso campo alla radice della richiesta non viene letto quando è impostato input.

# Raw upstream 'input': sent as written, in place of what our adapter builds.
# "resolution" sets the billed tier: 2K here, the default tier when left out.
resp = client.images.generate(
    model="nano-banana-2",
    prompt="",  # ignored when input is set
    extra_body={"input": {
        "prompt": "a futuristic cyberpunk skyline at dusk",
        "aspect_ratio": "16:9",
        "resolution": "2K",
        "output_format": "png",
    }},
)
Se passi input, salti la convalida di Kunavo. Se il servizio upstream rifiuta i tuoi campi, riceverai da noi un errore 422: consulta la documentazione sugli errori e la documentazione upstream del modello.

Prossimi passi