Podmiana osoby
Jeden zestaw narzędzi, dwie postaci. Modele jednowywołaniowe podmieniają twarz albo głowę w pojedynczym żądaniu; modele dwuwywołaniowe najpierw analizują klip, żebyś to ty wskazał, kto ma się zmienić.
Część modeli nie generuje nowej sceny, tylko podmienia osobę: przenosi twarz z jednego zdjęcia na drugie, wymienia całą głowę albo zastępuje całą postać w klipie wideo. Katalog zbiera je pod jedną etykietą, toolkit: "subject-swap", a występują w dwóch postaciach — jedno wywołanie albo dwa wywołania z wyborem pomiędzy nimi.
Poza tym nie mają w sobie nic wyjątkowego. To zwykłe zadania asynchroniczne, parametry pochodzą z ich własnego inputSchema, a wyniki trafiają do output.assets[].
toolkit mówi, że model należy do tej grupy
toolkit to etykieta klasyfikująca przy pozycji katalogu, obecna zarówno w uwierzytelnionej odpowiedzi GET /api/v1/models, jak i w katalogu publicznym. Dziś ma jedną wartość, subject-swap, a przy modelach spoza jakiegokolwiek zestawu tego pola w ogóle nie ma — sprawdzaj więc wartość, nigdy obecność klucza.
Pole jest wyłącznie informacyjne. Nie rozstrzyga, który klucz może wywołać model, jak wyceniane jest żądanie ani co model przyjmuje. Nieznaną wartość potraktuj tak jak nieznany wpis w badges: pomiń ją.
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'Każdy wiersz to jeden wywoływalny endpoint. O reszcie tej strony decyduje kolumna tasks: family z endpointem video-analyze to family dwuwywołaniowa, a towarzyszącego jej endpointu video-edit nie da się wywołać samodzielnie.
Którą postać mam przed sobą?
| Jedno wywołanie | Dwa wywołania | |
|---|---|---|
| Endpointy | Jeden endpoint edycyjny | video-analyze, potem video-edit, w tej samej family |
| Kto zostaje podmieniony | To, co znajdzie model. Żądanie nie ma żadnego pola wskazującego osobę | Cele wybrane przez ciebie, wskazane pozycją |
| Co wysyłasz | Docelowy obraz albo klip i twój obraz referencyjny, w jednym żądaniu | W pierwszym wywołaniu klip; w drugim wybrane pozycje, każda z własnymi obrazami referencyjnymi |
| Za co płacisz | Jedno zadanie | Dwa zadania, wyceniane osobno |
| Sprawdza się, gdy | W kadrze jest jeden oczywisty bohater | W kadrze jest kilka osób albo podmiana niewłaściwej osoby dużo kosztuje |
Obie postaci mają async: true: tworzysz zadanie, a wynik odbierasz odpytywaniem albo webhookiem.
Jedno wywołanie
Nic tu nie odbiega od dowolnego innego modelu edycyjnego. Przeczytaj inputSchema endpointu, wypełnij je i utwórz zadanie:
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, // jedna z pozycji z powyższego zapytania do katalogu
input: {
image_url: 'spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3',
reference_image_url: 'spicy://f/fil_01k3m8xb2c3d4e5f6g7h8j9k0m',
},
}),
});Nazwy pól różnią się między endpointami: jedne przyjmują prompt, seed albo format wyjścia, inne nic poza dwoma obrazami. Obowiązującą listą jest schemat; nie przenoś nazw pól z jednego endpointu na drugi. Referencją może być publiczny adres HTTPS, mały obraz w Base64 wpisany wprost w żądanie albo przesłany wcześniej URI spicy://; zobacz Pliki multimedialne.
Dwa wywołania
Pierwsze wywołanie ogląda twój klip i zgłasza, co w nim znalazło, w postaci uporządkowanego zestawu klatek podglądu. To ty decydujesz, które z nich podmienić. Właściwą pracę wykonuje drugie wywołanie.
twój klip ──▶ video-analyze ──▶ output.assets[0], [1], [2] … (jedna klatka na cel)
│
wybierasz pozycje │ 0 i 2
▼
video-edit (analysis_task_id + swap_targets) ──▶ gotowy klipDlaczego nie jednym wywołaniem? Bo w scenie z czterema osobami pojedyncze wywołanie musiałoby zgadywać, kogo miałeś na myśli, a chybione zgadywanie to gotowy, rozliczony film z podmienioną niewłaściwą osobą. Rozbicie pracy na dwa kroki czyni wybór jawnym i możliwym do sprawdzenia.
Przeanalizuj klip
Wyślij klip do endpointu video-analyze. Jeśli endpoint ma pole resolution, ustaw je wprost — drugie wywołanie musi działać na tej samej wartości, więc wybierz tu rozdzielczość, jaką ma mieć gotowy klip. Na tych endpointach cena analizy od niej nie zależy.
const analysis = await createTask({
model: analyze.model, // pozycja, której tasks zawierają "video-analyze"
input: {
video_url: 'spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3',
resolution: '720p',
},
});
const analysisTaskId = analysis.taskId;Odczytaj, co zostało znalezione
Poczekaj na zadanie tak jak zwykle. Udana analiza zwraca po jednej klatce podglądu na każdy śledzony cel, w 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
}Kolejność tej tablicy jest kontraktem. Indeks elementu to object_index, który odsyłasz w drugim wywołaniu: pierwsza klatka to 0, druga 1 i tak dalej. Pokaż klatki osobie, która wybiera, albo dopasuj je samodzielnie — tak czy inaczej odsyłaj pozycje z tej samej tablicy.
Analiza, która nie znajdzie nic do śledzenia, nie kończy się powodzeniem z pustą tablicą: kończy się jako failed z errorCode: "generation_failed", a blokada środków zostaje zwolniona w całości. Analiza ze stanem succeeded ma więc zawsze co najmniej jedną klatkę, a twojemu kodowi pozostaje tylko wybór między nimi.
Podmień wybrane cele
Wyślij identyfikator zadania analizy oraz po jednym wpisie swap_targets na każdy cel do podmiany. Cele, których nie wymienisz, pozostają nietknięte.
const swapped = await createTask({
model: edit.model, // pozycja "video-edit" z tej samej family
input: {
analysis_task_id: analysisTaskId,
resolution: '720p', // ta sama wartość, na której działała analiza
swap_targets: [
{ object_index: 0, image_urls: ['spicy://f/fil_01k3m8xb2c3d4e5f6g7h8j9k0m'] },
],
},
});Wynikiem jest jeden klip w output.assets[], jak przy każdym innym zadaniu wideo.
Co drugie wywołanie sprawdza, zanim je przyjmie
Te kontrole odbywają się przy tworzeniu zadania, więc odrzucone żądanie niczego nie tworzy i niczego nie blokuje.
| Reguła | Dlaczego |
|---|---|
| Analiza musi należeć do twojego konta i zakończyć się powodzeniem | Analiza w kolejce albo nieudana nie ma do czego się odwołać |
| W obu wywołaniach użyj tego samego klucza API | Analizę da się odczytać wyłącznie kluczem, który ją utworzył, więc innym w ogóle nie zobaczysz klatek, spośród których wybierasz |
Każdy object_index musi mieścić się w zakresie i wystąpić tylko raz | Poza zakresem podmieniona zostałaby po cichu niewłaściwa osoba, a przy powtórzeniu ta sama osoba dwa razy. Oba zadania dobiegłyby końca i zostałyby normalnie rozliczone |
Pola wpływające na cenę, na przykład resolution, muszą zgadzać się z analizą | Gdyby były dziedziczone, przestałbyś widzieć, według którego progu cenowego jesteś rozliczany |
| Klip źródłowy musi być ten sam | Pomiń video_url, a zostanie użyty klip z analizy; jeśli go wyślesz, musi być identyczny |
| Analiza nadaje się do użycia przez 24 godziny od zakończenia | Później znikają stojące za nią dane śledzenia, a często również klip źródłowy |
Ustaw `resolution` w obu wywołaniach albo uważnie sprawdź wartość domyślną
Pominięte pola przyjmują wartość default ze schematu, zanim oba żądania zostaną porównane. Analiza zostawiona na wartości domyślnej i podmiana zostawiona na wartości domyślnej faktycznie się zgadzają — ale analiza z ustawioną wartością i podmiana zostawiona pusta już nie, a rozbieżność zostaje odrzucona, zamiast być po cichu poprawiona. Jawne wysłanie wartości w obu wywołaniach zdejmuje ten problem z głowy.
Gdy drugie wywołanie zostaje odrzucone
Wszystkie te przypadki to HTTP 400 z kodem biznesowym code: 400. Pole msg przyjmuje jedną z wartości:
msg | Co się stało | Co zrobić |
|---|---|---|
The referenced analysis task was not found. | Na tym koncie nie ma takiego zadania albo analysis_task_id nie jest identyfikatorem zadania | Użyj taskId zwróconego przez twoją własną analizę. Zadanie z cudzego konta jest celowo zgłaszane tak samo |
The referenced task belongs to a different model. | Identyfikator wskazuje inne zadanie — często samo zadanie podmiany albo analizę z innej family | Wskaż zadanie video-analyze z tej samej family |
The referenced analysis did not complete successfully. | Analiza wciąż trwa albo zakończyła się jako failed / expired | Zaczekaj na nią albo uruchom ją ponownie |
That analysis has no reusable result. Run the analysis step again. | Analiza się powiodła, ale nie zostawiła niczego, co podmiana mogłaby wykorzystać | Uruchom analizę ponownie na tym samym klipie |
That analysis result has expired. Run the analysis step again. | Minęły ponad 24 godziny | Uruchom analizę ponownie |
"resolution" must match the analysis step. | Oba wywołania różnią się w polu wpływającym na cenę | Wyślij wartość z analizy albo uruchom nową analizę z wartością, której chcesz |
"video_url" must match the analysis step. | Wysłałeś inny klip | Wyślij ten sam URI albo pomiń to pole |
That analysis found N target(s); "object_index" is out of range. | Pozycja wykracza poza to, co zwróciła analiza | Pozycje liczone są od zera i kończą się na assets.length - 1 |
"object_index" is listed more than once. | Dwa wpisy wskazują ten sam cel | Przypisz jeden wpis na cel, a wszystkie jego obrazy referencyjne umieść w image_urls tego wpisu |
Ile kosztują oba wywołania
Analiza i podmiana to dwa osobne zadania z dwiema osobnymi cenami, każde z własną blokadą i własnym rozliczeniem. Odrzucone drugie wywołanie nic nie kosztuje; udana analiza zostaje rozliczona nawet wtedy, gdy potem zrezygnujesz z podmiany.
- Analiza jest zwykle wyceniana
per_request: jedno obciążenie na wywołanie, niezależnie od długości klipu i liczby znalezionych celów. - Podmiana jest zwykle wyceniana
per_second, w pełnych blokach, a długość bierze się z klipu, a nie z pola, które ustawiasz. Kwota podana przed wysłaniem dotyczy więc najdłuższego klipu, jaki endpoint przyjmuje: podana kwota jest kwotą zablokowaną, końcowe obciążenie liczy się z długości zaokrąglonej w górę i nigdy nie przekracza blokady, a różnica wraca na saldo przy rozliczeniu zadania. Blokada większa od końcowego obciążenia jest tutaj zachowaniem oczekiwanym. Zanim zaplanujesz na tym budżet, przeczytaj Rozliczenia.
Obie ceny odczytaj z katalogu, zamiast je zakładać, a jeśli liczy się dokładna kwota, poproś o wycenę każdego wywołania.
Następne kroki
Katalog modeli i schematy
Odczytaj toolkit, tasks, ceny i schemat wejścia każdego endpointu
Zadania asynchroniczne
Tworzenie zadania, odpytywanie i odczyt wyniku
Pliki multimedialne
Prześlij klip albo zdjęcie referencyjne i odbierz URI spicy://
Rozliczenia
Blokada, rozliczenie i naliczanie długości w pełnych blokach
Przewodnik: generowanie i edycja obrazów
Jak zachowują się endpointy obrazu jako grupa — dwie postacie żądania, trzy sposoby zapisu rozmiaru wyjścia, co dokłada do rachunku obraz referencyjny, wagi adaptera i jak czytać odmowę.
Integracja produkcyjna
Lista kontrolna przed startem integracji, która przetrwa awarie – właściwy model, chronione klucze i saldo, potwierdzone koszty, trwałe śledzenie zadań, niezawodne callbacki i testy do wykonania przed uruchomieniem.

