Nawet udana generacja może skończyć się niedostępnym obrazem w Twoim produkcie, jeśli zapiszesz tymczasowy URL dostarczania zamiast skopiować plik. Podpisane linki wygasają po kilku minutach; sama zawartość wygenerowana przez API jest przechowywana około dwóch tygodni.
API mediów przekazuje ten sam plik pod kilkoma postaciami: jako referencję wejściową, klucz wygenerowanego wyniku i podpisany URL pozwalający przez kilka minut pobrać bajty. Każda ma własny termin wygaśnięcia i żadna nie jest trwałym adresem. Ten przewodnik podaje dokładne okresy w SpicyAPI, a następnie pokazuje, jak kopiować cenne wyniki do magazynu pod Twoją kontrolą. Ustawienia retencji mogą skrócić te okresy.
Najpierw wypróbuj w Playground
Otwórz stronę Z-Image do generowania obrazu z tekstu i zaloguj się:
- Wpisz krótką scenę w Prompt i wybierz Aspect ratio.
- Naciśnij Run model. Wynik pojawi się pod Output.
- Pobierz obraz i zapisz go w miejscu, nad którym masz kontrolę.
- Później otwórz zapisany plik ponownie. Otworzy się bez strony i jej tymczasowego linku do wyniku — dalsza część przewodnika automatyzuje właśnie to w produkcji.
Najważniejsze wnioski
- Kopiuj po zakończeniu, w procesie roboczym. Duże pobrania wykonuj poza żądaniem callbacku.
- Zachowaj prywatność podpisanych linków. Ich posiadacz ma tymczasowy dostęp do pliku.
- Udostępniaj własny ID zasobu i URL dostarczania. Rekord zadania może istnieć dłużej niż jego media.
Co wygasa i kiedy
| Element | Czas ważności | Co dzieje się na końcu |
|---|---|---|
Bilet wysyłania (uploadUrl) | Około 20 minut; zarówno PUT, jak i zatwierdzenie muszą zakończyć się w tym czasie | Odpowiedź o wygasłym bilecie wymaga nowego; zajęte zatwierdzanie może też zwrócić 409 |
Zatwierdzona referencja (spicy://f/fil_...) | 1 dzień od zatwierdzenia, do expiresAt z odpowiedzi na zatwierdzenie | URI odrzucane w nowych zadaniach i ponowieniach |
Link do wyniku (output.assets[].url) | Około 20 minut, nigdy poza termin samego pliku | Magazyn odpowiada 403; odczytaj zadanie ponownie, aby uzyskać świeży link |
| Wygenerowany obraz, wideo lub dźwięk | Około 14 dni od zakończenia (krócej, jeśli tak ustawisz) | Usunięty; nie można wystawić linku (410 z download-url) |
| Tekst promptu i ścieżki plików wynikowych w zadaniu | 30 dni | input i output są czyszczone; rekord zadania pozostaje |
| Rekord zadania (ID, model, stan, koszt, znaczniki czasu) | Przechowywany bezterminowo | Nadal można go odpytać, ale bez treści |
Media krótkich filmów utworzone w projekcie Spicy Studio mają własny dłuższy okres, około 60 dni; generacje przez API, SDK i Playground korzystają z powyższego limitu 14 dni. Retencję generacji można też skrócić dla żądania lub konta, jak opisuje artykuł o ustawieniach retencji. Możesz jedynie skracać te limity, nigdy ich wydłużać.
14 dni liczy się od zakończenia, nie od wysłania. Zanim zadanie się skończy, znacznik czasu jest tymczasowy i zostaje przeliczony przy zakończeniu.
„Około” jest celowe. Sprzątanie magazynu działa według reguły cyklu życia i może nastąpić do dnia po terminie, lecz to opóźnienie nie daje Ci dodatkowego czasu: po zamknięciu okresu ważności nie jest już wystawiany link do pobrania.
Jeden plik, cztery reprezentacje
local reference image
│
├─ POST /api/v1/common/upload-url → upload ticket (about 20 min)
├─ PUT to the returned uploadUrl → temporary object
├─ POST /api/v1/files/{fileId}/commit → spicy://f/fil_... (usable 1 day)
├─ POST /api/v1/jobs/createTask → task
└─ terminal callback or recordInfo
└─ output.assets[] → key + url + expiresAt (about 20 min)
└─ stream bytes to your storage → your asset ID and delivery URLSDK, CLI i Playground wykonują za Ciebie kroki wysyłania, więc rzadko musisz pisać je ręcznie. Każda reprezentacja ma jedno zadanie. Bilet zezwala na bezpośredni zapis w okresie swojej ważności; zatwierdzenie utrwala zaakceptowane bajty. URI spicy:// pozwala przez dzień wskazywać zatwierdzone wejście przypisane do konta. Wyjściowy key identyfikuje wygenerowany wynik. url tymczasowo zezwala na odczyt. Twój ID zasobu jest jedyną trwałą tożsamością, którą produkt powinien udostępniać.
Wysłanie referencji do SpicyAPI jest bezpłatne; płacisz za zadanie generacji, które jej używa. Odświeżenie linku do istniejącego wyniku nie powoduje kolejnej opłaty za generację. Jeśli wynik zaginął i zdecydujesz się wygenerować go ponownie, nowe zadanie ma własną opłatę za model. Osobno zaplanuj koszty przechowywania, żądań i transferu w trwałym zasobniku według planu dostawcy magazynu.
Wejścia: wyślij i uwzględnij wygaśnięcie po 1 dniu
Trzymaj oryginalny obraz, wideo lub dźwięk referencyjny we własnym magazynie. Wyślij lokalne wejście przez SDK, CLI lub Playground i używaj zatwierdzonego URI ponownie, dopóki jest ważne. Po wygaśnięciu wyślij plik ponownie, gdy go potrzebujesz; tymczasowe URI nie jest Twoim archiwum.
Trzy sposoby przekazania wejścia modelowi:
- Publiczny adres HTTPS. Działa tam, gdzie pole modelu przyjmuje URL, pod warunkiem że zwraca sam plik. Link do dysku chmurowego, albumu lub serwisu społecznościowego zwraca stronę internetową i zadanie kończy się błędem
invalid_asset; zamiast tego pobierz plik i go wyślij. - Mały obraz osadzony. JPEG, PNG, WebP lub GIF jako URI danych base64, do 1 MiB każdy.
- Wysyłanie pliku. Dla plików lokalnych. Obrazy (JPEG, PNG, WebP, GIF) do 10 MiB; audio i wideo WAV, MP3, WebM, MP4 lub MOV do 90 MiB i 600 sekund. Model może przyjmować mniej, więc sprawdź limit pola na jego stronie. W TypeScript SDK
await client.uploadFile("./reference.png")zwraca URIspicy://jakofile.uri.
Nigdy nie ujawniaj klucza SpicyAPI w przeglądarce; bilety wysyłania powinien wystawiać Twój serwer. Dokumentacja mediów opisuje integrację z przeglądarką i pełne limity.
Wyjścia: jak wygląda gotowy wynik
Zakończone zadanie i możliwy do pobrania plik to różne stany:
- Gotowy: skopiuj plik do własnego magazynu i zapisz ID zadania, format oraz rozmiar.
- Oczekujący: zachowaj to samo zadanie i sprawdź ponownie; nie kupuj kolejnej generacji, aby uzyskać plik.
- Niedostępny: kopia na platformie zniknęła i czekanie jej nie przywróci. Użyj swojej zapisanej kopii; jeśli jej nie masz, wygeneruj ponownie, z normalną opłatą. Jedynie początkowy błąd przechowywania bez dostarczonych plików nie jest naliczany.
Tymczasowe linki dają dostęp posiadaczowi. Nie umieszczaj ich na publicznych stronach ani w narzędziach analitycznych; pobierz aktualny link, kiedy proces kopiowania jest gotowy.
Granica retencji
Rekord zadania, klucz wyniku ani odświeżony link pobierania nie zachowują bajtów. Skopiuj je przed terminem wygaśnięcia mediów.
Po zamknięciu okresu recordInfo zgłasza contentState: "expired", a input i output są czyszczone najpóźniej po 30 dniach. Jeśli samodzielnie zniszczysz treść zadania, stan to purged. Stan zadania expired oznacza coś innego: generacja przekroczyła limit czasu i nic nie powstało. Nie pokazuj obu z tą samą etykietą w interfejsie — jeden wymaga ponowienia, a drugiego nie da się odzyskać przez ponowienie.
Ustaw termin kopiowania znacznie przed granicą. Kopiuj po pierwszym zdarzeniu sukcesu, a następnie uruchamiaj uzgadnianie, które wylicza udane zadania bez trwałego zasobu i alarmuje, gdy wynikom zostało jeszcze kilka dni. Dzień 14 jest marginesem odzyskiwania, nie harmonogramem. Gdy ustawienie konta lub żądania skraca retencję, outputsExpireAt może nastąpić dużo wcześniej niż po 14 dniach, więc wyznaczaj alarmy z tego pola, nigdy ze stałej. Jedno ustawienie konta może też zadziałać wstecz: skrócenie czasu przechowywania tekstu promptu usuwa wyniki już zakończonych zadań wcześniej niż przypisane im outputsExpireAt. Pobierz to, co chcesz zachować, przed zmianą ustawień konta i oczekuj, że uzgadnianie zgłosi te zadania jako utracone, a nie jako błędy procesu.
Obsługa callbacków odporna na ponowienia i duże filmy
W aplikacji zweryfikuj zdarzenie zakończenia, zapisz je trwale i dodaj kopiowanie do kolejki przed wysłaniem odpowiedzi. Kopiowanie powinno być idempotentne, aby powtórzone zdarzenie nie tworzyło kolejnej pozycji galerii. Alarmuj o nieskopiowanych wynikach przed ich zaakceptowanym terminem wygaśnięcia.
Ten samodzielny proces roboczy zapisuje pliki istniejącego zadania w prywatnym lokalnym katalogu. Zainstaluj spicyapi, ustaw SPICY_API_KEY, TASK_ID i opcjonalnie ARCHIVE_DIR, a potem go uruchom. Nie tworzy żadnej generacji. Umieść katalog w trwałym magazynie i uwzględnij go w planie kopii zapasowych; nietrwały dysk serwera nie jest trwałym archiwum.
import hashlib
import json
import os
from pathlib import Path
import tempfile
import urllib.request
from spicyapi import SpicyClient, output_assets
client = SpicyClient()
task_id = os.environ["TASK_ID"]
root = Path(os.environ.get("ARCHIVE_DIR", "media-archive"))
root.mkdir(parents=True, exist_ok=True, mode=0o700)
task = client.get_task(task_id)
if task["state"] != "succeeded":
raise RuntimeError("Resume the existing task before archiving it.")
for index, asset in enumerate(output_assets(task)):
if asset.get("pending") or asset.get("unavailable") or not asset.get("url"):
raise RuntimeError("Asset is not ready; retain the task ID.")
request = urllib.request.Request(asset["url"], headers={"User-Agent": "SpicyAPI-Blog-Example/1.0"})
temporary = None
try:
with urllib.request.urlopen(request, timeout=300) as response:
mime = response.headers.get_content_type()
suffix = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp",
"video/mp4": ".mp4", "video/webm": ".webm", "audio/mpeg": ".mp3", "audio/wav": ".wav"}.get(mime)
if response.status != 200 or suffix is None:
raise RuntimeError("Unexpected response format; do not guess an extension.")
digest, size = hashlib.sha256(), 0
with tempfile.NamedTemporaryFile(dir=root, delete=False) as output:
temporary = Path(output.name)
while chunk := response.read(1024 * 1024):
output.write(chunk)
digest.update(chunk)
size += len(chunk)
if not size or (asset.get("bytes") and size != int(asset["bytes"])):
raise RuntimeError("Empty or incomplete transfer.")
filename = root / (digest.hexdigest() + suffix)
temporary.replace(filename)
record = {"taskId": task_id, "index": index, "file": filename.name,
"mime": mime, "bytes": size, "sha256": digest.hexdigest()}
(root / (digest.hexdigest() + ".json")).write_text(json.dumps(record, indent=2))
print(filename)
finally:
if temporary is not None:
temporary.unlink(missing_ok=True)Przy dostarczaniu z magazynu obiektowego przesyłaj strumieniowo do prywatnego obiektu tymczasowego i udostępniaj go dopiero po pełnym transferze. Dokumentacja mediów i webhooków opisuje odświeżanie wyników i uwierzytelnione zdarzenia. Zachowaj odpytywanie jako ścieżkę uzgadniania dla pominiętych callbacków.
Twój trwały zasób powinien ukrywać ścieżkę generacji
Po skopiowaniu obiekt udostępniany przez produkt ma własną tożsamość:
asset_01k... database identity
generated/job_.../....mp4 private storage key
https://media.example.com/a/... controlled delivery URLZachowaj prywatność zasobnika i dostarczaj przez własny CDN, uwierzytelniony endpoint mediów lub własne krótkotrwałe linki. Nie używaj podpisanego URL SpicyAPI jako publicznego adresu. Dzięki temu tymczasowe linki i szczegóły przechowywania nie trafiają do HTML, logów i eksportów, możesz zmieniać reguły dostępu lub domeny bez przepisywania historii zadań, a usunięcie zasobu użytkownika jest operacją na Twoim rekordzie. Zachowaj prywatnie pochodzenie: źródłowy ID zadania, oryginalny klucz zasobu, typ zawartości, rozmiar, czas kopiowania i klucz docelowy.
Awarie i właściwe działania naprawcze
| Awaria | Co obserwujesz | Właściwe odzyskiwanie |
|---|---|---|
| Bilet wygasł przed końcem wysyłania lub użyto niezatwierdzonego pliku | Wysyłanie lub zatwierdzenie odrzucone, albo tworzenie zadania odmawia przyjęcia referencji | Poproś o nowy bilet i wyślij ponownie; 409/429 przy zajętym zatwierdzaniu oznacza ponowienie tego samego zatwierdzenia |
Zapisane URI spicy:// użyte następnego dnia | 400 podczas tworzenia zadania albo nieudane ponowienie | Wyślij oryginał ponownie; przechowuj oryginały we własnym magazynie |
| Callback dostarczony dwa razy | Dwa procesy rozpoczynają tę samą kopię | Usuń duplikaty według request_id oraz ID zadania i indeksu zasobu w kolejce i miejscu docelowym |
| Endpoint callbacku jest wolny | Ponowienia przychodzą podczas pobierania | Zapisz, dodaj do kolejki i zwróć 2xx przed przenoszeniem mediów |
| Link wyniku wygasł w kolejce | Pobranie ze źródła zwraca 403 | Odczytaj zadanie ponownie po świeży url i ponów strumieniowanie |
Zasób ma pending: true | key i url są puste | Odczekaj kilka sekund i odpytaj ponownie |
Zasób ma unavailable: true lub download-url zwraca 410 | Wygasł, został usunięty lub nie udało się go zapisać | Użyj zweryfikowanej prywatnej kopii; jeśli jej nie ma, poinformuj użytkownika i wygeneruj ponownie jako nowe płatne zadanie |
| Kopia skończona, zapis w bazie nieudany | Prywatny obiekt istnieje, brak rekordu gotowego zasobu | Zweryfikuj istniejący cel i ponów zapis metadanych; nigdy nie usuwaj poprawnej kopii ani nie generuj ponownie |
| Baza wskazuje gotowość, ale brakuje obiektu | Niedziałająca strona lub 404 z CDN | Uzgodnij wiersze bazy z zasobnikiem przed oznaczeniem gotowości |
| Podpisany URL widoczny w logach | Ujawniona tymczasowa możliwość odczytu | Ukryj ciągi zapytań i treść odpowiedzi; linki wygasają po około 20 minutach, ale nie można ich unieważnić |
| Duże wideo buforowane w pamięci | Proces restartuje się lub wyczerpuje pamięć | Strumieniuj z ograniczoną współbieżnością i kontrolą naporu danych |
Lista przed uruchomieniem produkcyjnym
- Odzyskaj przed wygaśnięciem: alarmuj o brakujących kopiach według zaakceptowanego terminu każdego zadania.
- Jednoznacznie określ właściciela: udostępniaj prywatne zasoby tylko właścicielowi i zbierz wyniki przed rotacją kluczy API.
- Usuwaj we wszystkich systemach: uwzględnij zasobnik, pamięć podręczną dostarczania i kopie zapasowe w polityce usuwania.
Częste pytania
Czy mogę zapisać URL zwrócony w output.assets[].url?
Tylko na potrzeby kopiowania. Zapisz własny rekord zasobu i pobierz świeży link, gdy chcesz przenieść bajty.
Czy nadal potrzebuję download-url?
Tylko do odświeżenia linku przez taskId i key w okresie retencji; gotowe zasoby zawierają już url i expiresAt.
Czy odświeżenie linku wydłuża przechowywanie mediów?
Nie. Nowy link odnawia tylko dostęp do odczytu; obiekt nadal zostaje usunięty w retention.outputsExpireAt.
Czy mogę przekazać modelowi publiczny URL obrazu zamiast go wysyłać?
Tak, gdy schemat modelu dopuszcza URL w tym polu, jeśli adres zwraca sam plik i pozostaje dostępny podczas zadania. Użyj wysyłania pliku dla plików lokalnych, większych wejść lub treści bez publicznego hostingu.
Potrzebuję tego samego obrazu referencyjnego za tydzień. Co zrobić?
Wyślij go ponownie z własnej kopii, kiedy potrzebujesz go za tydzień. Zatwierdzona referencja działa tylko przez 1 dzień, ale w tym czasie może obsłużyć wiele zadań, o ile nie zostanie wcześniej usunięta.
Czy kopiować wynik wewnątrz żądania callbacku?
Nie. Duży transfer w żądaniu przekracza 15-sekundowy limit czasu callbacku i wywołuje zduplikowane dostarczenia; proces w kolejce może bezpiecznie ponawiać.
A jeśli w ogóle nie chcę zachować wyniku?
Nie kopiuj go i pozwól mu wygasnąć, albo skróć retencję bądź zniszcz treść zadania na żądanie. Te ustawienia opisują Wysyłanie, retencja i dostarczanie mediów AI oraz dokumentacja retencji. Zniszczenie treści nigdy nie zmienia naliczonej opłaty.
Pozwól agentowi obsłużyć wywołanie
Użyj agenta, aby odzyskać istniejący wynik zamiast tworzyć go ponownie.
- CLI:
npx @spicyapi/cli tasks get "$TASK_ID"pobiera zadanie. Zobacz przewodnik CLI. - MCP: poproś
spicyapi_task_geto aktualny wynik i zapisz zwrócony plik. Zobacz przewodnik MCP. - Skill: zainstaluj
npx skills add spicyapi-ai/spicy-skill, potem poproś$spicyapio zarchiwizowanie wyniku zadania w wybranym katalogu. Zobacz przewodnik Skill.







