Sostituzione di persone
Un solo toolkit, due forme. I modelli a una chiamata sostituiscono un volto o una testa in un’unica richiesta; quelli a due chiamate analizzano prima la clip, così sei tu a scegliere chi cambia.
Alcuni modelli non generano una scena nuova: sostituiscono una persona. Un volto portato da una foto a un’altra, una testa intera oppure un’intera figura dentro una clip video. Il catalogo li raccoglie sotto un’unica etichetta, toolkit: "subject-swap", e si presentano in due forme: una chiamata oppure due chiamate con una scelta in mezzo.
Per il resto non hanno nulla di particolare. Sono normali attività asincrone, i loro parametri arrivano dal rispettivo inputSchema e i risultati compaiono in output.assets[].
toolkit ti dice che il modello è uno di questi
toolkit è un’etichetta di classificazione su una voce del catalogo ed è presente sia nella risposta autenticata di GET /api/v1/models sia nel catalogo pubblico. Oggi ha un solo valore, subject-swap, e nei modelli che non appartengono ad alcun toolkit il campo non c’è affatto: controlla quindi il valore, mai la presenza della chiave.
È un campo puramente informativo. Non decide quale chiave può chiamare un modello, come viene calcolato il prezzo della richiesta o che cosa il modello accetta. Un valore che non conosci va trattato come una voce sconosciuta di badges: ignoralo.
curl -s "https://api.spicyapi.ai/api/v1/models?modality=video" \
-H "Authorization: Bearer $SPICY_API_KEY" \
| jq -r '.data.items[]
| select(.toolkit == "subject-swap" and .enabled and .available)
| [.model, (.tasks | join(",")), .family] | @tsv'Ogni riga è un endpoint richiamabile. A decidere tutto il resto di questa pagina è la colonna tasks: una family che ha un endpoint video-analyze è una family a due chiamate, e l’endpoint video-edit che la accompagna non si può chiamare da solo.
Quale delle due forme ho davanti?
| Una chiamata | Due chiamate | |
|---|---|---|
| Endpoint | Un solo endpoint di modifica | video-analyze, poi video-edit, nella stessa family |
| Chi viene sostituito | Chi trova il modello. La richiesta non ha alcun campo per indicare una persona | I soggetti che hai scelto, indicati dalla loro posizione |
| Che cosa invii | L’immagine o la clip di partenza più l’immagine di riferimento, in un’unica richiesta | Nella prima chiamata la clip; nella seconda le posizioni scelte, ciascuna con le proprie immagini di riferimento |
| Che cosa paghi | Un’attività | Due attività, con prezzi separati |
| Adatta quando | Nell’inquadratura c’è un solo soggetto evidente | Nell’inquadratura ci sono più persone, oppure sbagliare persona costa caro |
Entrambe le forme sono async: true: crei un’attività e poi recuperi il risultato con il polling o con un webhook.
Una chiamata
Qui non cambia nulla rispetto a qualsiasi altro modello di modifica. Leggi l’inputSchema dell’endpoint, compilalo, crea l’attività:
const created = await fetch('https://api.spicyapi.ai/api/v1/jobs/createTask', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SPICY_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-8814-swap-1',
},
body: JSON.stringify({
model: swap.model, // una delle voci della query al catalogo qui sopra
input: {
image_url: 'spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3',
reference_image_url: 'spicy://f/fil_01k3m8xb2c3d4e5f6g7h8j9k0m',
},
}),
});I nomi dei campi cambiano da un endpoint all’altro: alcuni accettano un prompt, un seed o un formato di uscita, altri nient’altro che le due immagini. L’elenco valido è lo schema; non portare i nomi dei campi da un endpoint a un altro. Un riferimento può essere un URL HTTPS pubblico, una piccola immagine in Base64 scritta direttamente nella richiesta oppure un URI spicy:// già caricato; vedi File multimediali.
Due chiamate
La prima chiamata guarda la tua clip e riferisce che cosa ci ha trovato, sotto forma di una serie ordinata di fotogrammi di anteprima. Sei tu a decidere quali sostituire. Il lavoro vero e proprio lo fa la seconda chiamata.
la tua clip ──▶ video-analyze ──▶ output.assets[0], [1], [2] … (un fotogramma per soggetto)
│
scegli le posizioni │ 0 e 2
▼
video-edit (analysis_task_id + swap_targets) ──▶ la clip finitaPerché non in una sola chiamata? Perché in una scena con quattro persone una chiamata sola dovrebbe indovinare a chi ti riferivi, e un’ipotesi sbagliata è un video finito e addebitato in cui è stata sostituita la persona sbagliata. Dividere il lavoro rende la scelta esplicita e verificabile.
Analizza la clip
Invia la clip all’endpoint video-analyze. Imposta resolution in modo esplicito se l’endpoint ha quel campo: la seconda chiamata dovrà girare sullo stesso valore, quindi scegli qui la risoluzione che vuoi per la clip finita. Su questi endpoint il prezzo dell’analisi non dipende da essa.
const analysis = await createTask({
model: analyze.model, // la voce i cui tasks contengono "video-analyze"
input: {
video_url: 'spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3',
resolution: '720p',
},
});
const analysisTaskId = analysis.taskId;Leggi che cosa ha trovato
Attendi l’attività come al solito. Un’analisi riuscita restituisce un fotogramma di anteprima per ogni soggetto tracciato, in output.assets[]:
{
"state": "succeeded",
"output": {
"assets": [
{ "url": "https://…", "expiresAt": "2026-09-20T09:32:11Z", "mime": "image/jpeg" },
{ "url": "https://…", "expiresAt": "2026-09-20T09:32:11Z", "mime": "image/jpeg" }
]
},
"settled": true
}L’ordine di quell’array è il contratto. L’indice di ogni elemento è l’object_index che rimandi indietro nella seconda chiamata: il primo fotogramma è 0, il secondo 1, e così via. Mostra i fotogrammi a chi deve scegliere, oppure abbinali tu; in ogni caso rimanda posizioni prese da questo stesso array.
Un’analisi che non trova nulla da tracciare non riesce restituendo un array vuoto: finisce come failed con errorCode: "generation_failed", e il blocco viene liberato per intero. Perciò un’analisi in stato succeeded ha sempre almeno un fotogramma, e al tuo codice resta solo da scegliere tra quelli.
Sostituisci i soggetti che hai scelto
Invia l’ID dell’attività di analisi e una voce di swap_targets per ogni soggetto da sostituire. I soggetti che ometti restano intatti.
const swapped = await createTask({
model: edit.model, // la voce "video-edit" della stessa family
input: {
analysis_task_id: analysisTaskId,
resolution: '720p', // lo stesso valore con cui è girata l’analisi
swap_targets: [
{ object_index: 0, image_urls: ['spicy://f/fil_01k3m8xb2c3d4e5f6g7h8j9k0m'] },
],
},
});Il risultato è una clip in output.assets[], come in qualsiasi altra attività video.
Che cosa verifica la seconda chiamata prima di accettare
Questi controlli avvengono alla creazione, quindi una richiesta rifiutata non crea nulla e non blocca nulla.
| Regola | Perché |
|---|---|
| L’analisi deve appartenere al tuo account ed essere riuscita | Un’analisi in coda o fallita non ha nulla a cui puntare |
| Usa la stessa chiave API per entrambe le chiamate | Un’analisi si rilegge solo con la chiave che l’ha creata, quindi con un’altra non vedi nemmeno i fotogrammi tra cui stai scegliendo |
Ogni object_index deve rientrare nell’intervallo e comparire una volta sola | Fuori intervallo verrebbe sostituita in silenzio la persona sbagliata; ripetuto, la stessa persona verrebbe sostituita due volte. In entrambi i casi l’attività finirebbe e verrebbe addebitata normalmente |
I campi che incidono sul prezzo, come resolution, devono coincidere con l’analisi | Ereditarli nasconderebbe la fascia di prezzo su cui vieni addebitato |
| La clip di partenza deve essere la stessa | Ometti video_url e viene usata la clip analizzata; se la invii, deve essere identica |
| L’analisi resta utilizzabile per 24 ore dalla fine | Dopo di che i dati di tracciamento alle sue spalle non ci sono più e spesso nemmeno la clip di partenza |
Imposta `resolution` in entrambe le chiamate, oppure leggi con attenzione il valore predefinito
I campi omessi assumono il valore default del loro schema prima che le due richieste vengano confrontate. Un’analisi lasciata al valore predefinito e una sostituzione lasciata al valore predefinito coincidono davvero, ma un’analisi in cui hai impostato un valore e una sostituzione lasciata vuota no, e la discrepanza viene rifiutata anziché corretta in silenzio. Inviare il valore in modo esplicito in entrambe le chiamate elimina il dubbio.
Quando la seconda chiamata viene rifiutata
Sono tutti HTTP 400 con codice di business code: 400. Il campo msg vale uno di questi:
msg | Che cosa è successo | Che cosa fare |
|---|---|---|
The referenced analysis task was not found. | Su questo account non esiste quell’attività, oppure analysis_task_id non è un ID di attività | Usa il taskId restituito dalla tua analisi. Un’attività di un altro account viene segnalata allo stesso modo, di proposito |
The referenced task belongs to a different model. | L’ID punta a un’altra attività: spesso l’attività di sostituzione stessa, oppure l’analisi di un’altra family | Fallo puntare all’attività video-analyze della stessa family |
The referenced analysis did not complete successfully. | L’analisi è ancora in corso oppure è finita come failed / expired | Aspettala oppure rilanciala |
That analysis has no reusable result. Run the analysis step again. | L’analisi è riuscita ma non ha lasciato nulla di utilizzabile per la sostituzione | Rilancia l’analisi sulla stessa clip |
That analysis result has expired. Run the analysis step again. | Sono passate più di 24 ore | Rilancia l’analisi |
"resolution" must match the analysis step. | Le due chiamate non concordano su un campo che incide sul prezzo | Invia il valore dell’analisi, oppure lancia una nuova analisi con il valore che vuoi |
"video_url" must match the analysis step. | Hai inviato una clip diversa | Invia lo stesso URI, oppure ometti il campo |
That analysis found N target(s); "object_index" is out of range. | Una posizione va oltre quelle restituite dall’analisi | Le posizioni partono da zero e si fermano a assets.length - 1 |
"object_index" is listed more than once. | Due voci indicano lo stesso soggetto | Assegna una sola voce per soggetto, con tutte le sue immagini di riferimento nell’image_urls di quella voce |
Quanto costano le due chiamate
Analisi e sostituzione sono due attività distinte con due prezzi distinti, ciascuna bloccata e addebitata per conto proprio. Una seconda chiamata rifiutata non costa nulla; un’analisi riuscita viene addebitata anche se poi decidi di non sostituire niente.
- L’analisi di solito ha prezzo
per_request: un addebito per chiamata, qualunque sia la durata della clip e quanti soggetti trovi. - La sostituzione di solito ha prezzo
per_second, a blocchi interi, e la durata arriva dalla clip e non da un campo che imposti tu. L’importo indicato prima dell’invio vale quindi per la clip più lunga che l’endpoint accetta: l’importo indicato è quello bloccato, l’addebito finale si calcola sulla durata arrotondata per eccesso e non supera mai il blocco, e la differenza torna sul tuo saldo quando l’attività viene chiusa. Vedere un blocco più alto dell’addebito finale, qui, è normale. Leggi Fatturazione prima di farci un budget.
Leggi entrambi i prezzi dal catalogo invece di darli per scontati e, se l’importo esatto conta, chiedi un preventivo per ciascuna chiamata.
Passi successivi
Catalogo dei modelli e schemi
Leggi toolkit, tasks, prezzi e lo schema di input di ogni endpoint
Attività asincrone
Creare un’attività, consultarla e leggerne l’output
File multimediali
Carica una clip o una foto di riferimento e ottieni un URI spicy://
Fatturazione
Blocco, addebito e durata fatturata a blocchi interi
Guida: generare e modificare immagini
Come si comportano gli endpoint immagine in quanto categoria — le due forme di richiesta, i tre modi di scrivere la dimensione in uscita, cosa aggiunge al conto un'immagine di riferimento, i pesi di adattamento e come leggere un rifiuto.
Integrazione in produzione
Una checklist di lancio per un’integrazione capace di riprendersi dagli imprevisti, con il modello giusto, chiavi e saldo protetti, costi confermati, tracciamento durevole delle attività, callback affidabili e i test da eseguire prima di andare online.

