Przejdź do treści głównej

API AI zwróciło klucz zasobu. Teraz zadbaj o trwałość mediów

Wyniki są przechowywane około 14 dni, referencje 1 dzień. Poznaj terminy wygaśnięcia i kopiuj pliki do własnego magazynu po callbacku.

Autor
SpicyAPI Engineering
Opublikowano
Czas czytania
11 min czytania
Zaktualizowano
Dłoń przesuwa świecącą kartę ze zdjęciem z tymczasowej stacji z klepsydrą do zatoki dysku obok monitora do montażu

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ę:

  1. Wpisz krótką scenę w Prompt i wybierz Aspect ratio.
  2. Naciśnij Run model. Wynik pojawi się pod Output.
  3. Pobierz obraz i zapisz go w miejscu, nad którym masz kontrolę.
  4. 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

ElementCzas ważnościCo 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 czasieOdpowiedź 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 zatwierdzenieURI odrzucane w nowych zadaniach i ponowieniach
Link do wyniku (output.assets[].url)Około 20 minut, nigdy poza termin samego plikuMagazyn odpowiada 403; odczytaj zadanie ponownie, aby uzyskać świeży link
Wygenerowany obraz, wideo lub dźwiękOkoł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 zadaniu30 dniinput i output są czyszczone; rekord zadania pozostaje
Rekord zadania (ID, model, stan, koszt, znaczniki czasu)Przechowywany bezterminowoNadal 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

text
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 URL

SDK, 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 URI spicy:// jako file.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.

python
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ść:

text
asset_01k...                     database identity
generated/job_.../....mp4        private storage key
https://media.example.com/a/...  controlled delivery URL

Zachowaj 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

AwariaCo obserwujeszWłaściwe odzyskiwanie
Bilet wygasł przed końcem wysyłania lub użyto niezatwierdzonego plikuWysyłanie lub zatwierdzenie odrzucone, albo tworzenie zadania odmawia przyjęcia referencjiPoproś 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 dnia400 podczas tworzenia zadania albo nieudane ponowienieWyślij oryginał ponownie; przechowuj oryginały we własnym magazynie
Callback dostarczony dwa razyDwa 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 wolnyPonowienia przychodzą podczas pobieraniaZapisz, dodaj do kolejki i zwróć 2xx przed przenoszeniem mediów
Link wyniku wygasł w kolejcePobranie ze źródła zwraca 403Odczytaj zadanie ponownie po świeży url i ponów strumieniowanie
Zasób ma pending: truekey i url są pusteOdczekaj kilka sekund i odpytaj ponownie
Zasób ma unavailable: true lub download-url zwraca 410Wygasł, 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 nieudanyPrywatny obiekt istnieje, brak rekordu gotowego zasobuZweryfikuj istniejący cel i ponów zapis metadanych; nigdy nie usuwaj poprawnej kopii ani nie generuj ponownie
Baza wskazuje gotowość, ale brakuje obiektuNiedziałająca strona lub 404 z CDNUzgodnij wiersze bazy z zasobnikiem przed oznaczeniem gotowości
Podpisany URL widoczny w logachUjawniona tymczasowa możliwość odczytuUkryj 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ęciProces 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_get o aktualny wynik i zapisz zwrócony plik. Zobacz przewodnik MCP.
  • Skill: zainstaluj npx skills add spicyapi-ai/spicy-skill, potem poproś $spicyapi o zarchiwizowanie wyniku zadania w wybranym katalogu. Zobacz przewodnik Skill.

Także w językach English · 日本語 · 한국어 · Deutsch · Français · Español · Português (Brasil) · Русский · Italiano

Wypróbuj samodzielnie

Płacisz tylko za to, co uruchomisz — bez planów i subskrypcji.

Zrobione w MiniMax H3 · ByteDance Seedance 2.0 Fast