Błędy
Wszystkie kody biznesowe, odpowiadające im statusy HTTP, sposoby rozwiązania problemów oraz błędy, przy których warto ponowić żądanie.
Gdy wywołanie się nie powiedzie, odpowiedź przekazuje dwie informacje: liczbę (code) określającą rodzaj problemu i zdanie (msg) przeznaczone dla człowieka. Ta strona opisuje wszystkie kody, ich znaczenie oraz to, czy ponowienie może pomóc. Jeżeli potrzebujesz szybkiego rozwiązania konkretnego problemu, zacznij od Rozwiązywania problemów.
Wspólna struktura odpowiedzi
Odpowiedzi JSON natywnego API zadań mają tę strukturę — te same cztery pola otaczające każdą odpowiedź. Odpowiedzi zgodności /v1 i SSE odczytuj zgodnie z ich własnymi protokołami:
{ "code": 200, "msg": "success", "data": { }, "request_id": "req_…" }codejest kodem biznesowym, nie statusem HTTP.200oznacza powodzenie.- Przy błędzie pole
datajest nieobecne (nie ma wartościnull) — otrzymujeszcode,msgirequest_id. Jedyny wyjątek: niektóre błędy biblioteki zasobów dodajądata.reason. msgjest dla ludzi: jego treść może się zmieniać, podobnie jak język (szczegóły niżej). Nigdy nie uzależniaj logiki od tego tekstu; korzystaj zcode.request_idjest również zwracane w nagłówkuX-Request-Id. Podawaj je przy zgłaszaniu problemu.
Status HTTP również jest ustawiany prawidłowo. Możesz kierować logikę według jednego lub drugiego — ale tylko jednego: konkretne kody są różne (niewystarczające saldo daje code: 40201 i HTTP 402), a ich mieszanie prowadzi w końcu do pominięcia przypadku.
Zalecamy korzystanie z code. 404 mówi tylko „nie znaleziono”; 40201 wskazuje niewystarczające dostępne saldo.
Nie każda odpowiedź jest w formacie JSON
Odpowiedź, której nie można odczytać jako JSON — na przykład tekst lub strona HTML z naszej sieci brzegowej — nie ma code. Obsłuż ją według statusu HTTP, zapisz początek treści i nie ponawiaj 403 bez sprawdzenia przyczyny: może oznaczać blokadę regionalną, której ponowienie nie zmieni.
Język komunikatów o błędach
Tekst błędu przeznaczony dla człowieka może być zwracany w wybranym języku. Domyślnie jest to angielski. Wybór obejmuje wyłącznie te trzy miejsca:
msgwe wspólnej strukturze odpowiedzi;errorMessagezadania zakończonego niepowodzeniem, zarówno wrecordInfo, jak i w callbackach webhooków (z jednym wyjątkiem opisanym niżej);error.messagew obiektach błędów zgodnych endpointów, niezależnie od formatu OpenAI, Anthropic lub Google Gemini (/v1beta).
Obsługiwane kody języków: en, ja, ko, de, fr, es, pt-BR, ru, it, pl.
Wyjątek: jeżeli sam model poda konkretny powód niepowodzenia — na przykład obraz referencyjny, którego nie mógł odczytać, lub ograniczenie praw autorskich dla wygenerowanego dźwięku — errorMessage zawiera ten powód po angielsku, z usuniętymi szczegółami wewnętrznymi, takimi jak nazwy usług, hosty, adresy URL i identyfikatory żądań. Dzięki temu widzisz rzeczywistą przyczynę. Powody dotyczące stanu konta lub rozliczeń nie są przekazywane dalej; zamiast nich otrzymujesz nasz własny komunikat. Content-Language nadal opisuje msg, a logika zawsze powinna korzystać z errorCode.
Wybór języka
Obowiązuje pierwsza pasująca opcja:
- Nagłówek żądania
Accept-Language, tylko dla tego żądania. Stosujemy standardowe wagi HTTP: wybieramy rozpoznany język o najwyższymq. Warianty regionalne są sprowadzane do języka —de-DEjest traktowane jakode, apt-PTjakopt-BR. Chiński nie jest dostępny, więc żaden jego znacznik nie pasuje i sprawdzamy kolejną opcję.*oznacza brak preferencji. - Ustawienie konta, w sekcji Ustawienia → Język błędów API w konsoli. Obejmuje każde żądanie z kluczem API tego konta bez rozpoznawalnego
Accept-Language. Większość serwerowych SDK i curl nie wysyła tego nagłówka domyślnie, dlatego większość integracji korzysta z ustawienia konta. Callbacky webhooków nie mają nagłówka żądania, na którym można oprzeć wybór, więc zawsze korzystają z ustawienia konta. - W pozostałych przypadkach — angielski.
Odpowiedzi błędów zawierają nagłówek Content-Language wskazujący język rzeczywiście użyty w komunikacie.
Zmienia się tylko tekst przeznaczony dla człowieka
code, errorCode, zgodne z OpenAI type i code, zgodne z Gemini code i status oraz wszystkie nazwy pól są jednakowe w każdym języku. Kieruj logikę według kodów, nigdy treści komunikatów; zapisuj code i request_id w logach i alertach.
Przykład
Ten sam kod 40201 (niewystarczające saldo) domyślnie wraca po angielsku:
HTTP/1.1 402 Payment Required
Content-Language: en
{ "code": 40201, "msg": "Insufficient balance", "request_id": "req_…" }Dodaj nagłówek Accept-Language: ja; pozostała część jest takim samym wywołaniem tworzenia zadania jak w szybkim starcie:
curl -X POST https://api.spicyapi.ai/api/v1/jobs/createTask \
-H "Authorization: Bearer $SPICY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
-H "Accept-Language: ja" \
--data "$TASK_PAYLOAD"msg jest teraz po japońsku; code nadal wynosi 40201:
HTTP/1.1 402 Payment Required
Content-Language: ja
{ "code": 40201, "msg": "残高が不足しています", "request_id": "req_…" }Kody ogólne
code | HTTP | Znaczenie | Ponowić? |
|---|---|---|---|
200 | 200 | Powodzenie | — |
400 | 400 | Nieprawidłowa struktura żądania lub parametry | Nie. Popraw i wyślij ponownie |
40003 | 400 | Rozmiar, typ lub zawartość przesłanego pliku nie odpowiada deklaracji podanej przy żądaniu adresu przesyłania | Nie. Poproś o nowy adres z prawidłowymi contentType i rozmiarem, prześlij ponownie i zatwierdź |
40004 | 400 | Żądanie jest prawidłowe, ale model obecnie nie może obsłużyć takiego zestawu ustawień | Nie. Zmień ustawienie wskazane w msg (na przykład rozdzielczość lub proporcje) albo wybierz inny model. Zobacz niżej |
401 | 401 | Uwierzytelnienie nie powiodło się; msg wskazuje jeden z trzech przypadków | Nie. Popraw nagłówek lub klucz |
403 | 403 | Brak uprawnienia. Przy wywołaniu API z prawidłowym kluczem oznacza ograniczenie konta — szczegóły niżej. Niezweryfikowany adres e-mail daje zamiast tego 40310 | Nie |
404 | 404 | Zasób nie istnieje lub nie należy do Ciebie | Nie |
409 | 409 | Konflikt z bieżącym stanem | Czasami — zobacz niżej |
40901 | 409 | Wycena wygasła, żądanie się zmieniło lub cena się zmieniła | Uzyskaj i zatwierdź nową wycenę; przyjęte żądania odzyskuj z pierwotnym kluczem idempotencji |
413 | 413 | Treść żądania przekracza rozmiar przyjmowany przez serwer | Nie. Zmniejsz treść lub prześlij pliki przez proces przesyłania mediów |
429 | 429 | Zbyt wiele żądań | Zwykle. Odczekaj zgodnie z Retry-After. Jeden przypadek 429 nie dotyczy szybkości — zobacz Limity częstotliwości |
500 | 500 | Błąd po naszej stronie | Tak. Odczekaj i ponów |
503 | 503 | Usługa pomocnicza (wyceny, zapisywanie obrazów inline, przesyłanie mediów) jest chwilowo niedostępna | Tak. Jeżeli występuje Retry-After, odczekaj wskazany czas i ponów — przy createTask z tym samym Idempotency-Key. Jeżeli problem trwa bez Retry-After, zastosuj wskazówkę z msg (na przykład publiczny adres HTTPS zamiast obrazu inline) |
503 i 50301 mają ten sam status HTTP, ale są różnymi kodami biznesowymi: 503 dotyczy usługi pomocniczej, a 50301 samego modelu.
Rozróżnianie trzech błędów 401
Trzy różne błędy uwierzytelnienia dają 401 i wymagają różnych poprawek. W każdym przypadku code pozostaje 401; rozróżnia je msg.
msg (po angielsku) | Rzeczywista przyczyna | Co poprawić |
|---|---|---|
Missing credentials: send your API key in the Authorization header as "Bearer sk-spicy-..." (w zgodnych endpointach /v1 i /v1beta: …as "Authorization: Bearer sk-spicy-...", x-api-key, or x-goog-api-key) | Żądanie dotarło bez żadnych danych uwierzytelniających | Klient pomija nagłówek. Sprawdź interceptor, proxy i to, czy zmienna środowiskowa została wczytana do uruchomionego procesu |
API key format is not recognized: keys begin with sk-spicy- | Dane uwierzytelniające dotarły, ale nie są naszym kluczem | Wklejono wartość sesji konsoli lub klucz innego dostawcy. Klucze SpicyAPI zaczynają się od sk-spicy-; echo "${SPICY_API_KEY:0:9}" powinno wypisać dokładnie ten prefiks |
Credentials are invalid or expired | Klucz ma nasz format, lecz nie istnieje, został unieważniony lub wygasł | Sprawdź klucz na stronie kluczy API; jeżeli został unieważniony lub wygasł, utwórz nowy. Ponowienie nigdy nie zmieni tej odpowiedzi w 200 |
Środkowy przypadek jest rozstrzygany bez odczytu bazy danych, dlatego przekazuje informację niedostępną w trzecim: taki ciąg znaków od początku nie mógł zadziałać. Trzeci komunikat jest celowo identyczny dla nieistniejącego, unieważnionego i wygasłego klucza, aby nie pozwalał sprawdzać, które klucze istnieją.
Tutaj pokazujemy te komunikaty po angielsku; msg korzysta z opisanego wyżej wyboru języka, a jego dokładna treść nie jest częścią kontraktu. Kieruj logikę według code, a komunikat traktuj jako tekst do przeczytania, wyświetlenia lub zapisania w logu — nie do porównywania w warunkach.
Konto nie może wywoływać API
W obu przypadkach sam klucz jest prawidłowy; to konto nie może jeszcze korzystać z API.
40310 oznacza niezweryfikowany adres e-mail. Komunikat brzmi Verify your account email to run models and call the API…:
- Otwórz konsolę. Baner u góry informuje o niezweryfikowanym adresie i pozwala ponownie wysłać wiadomość weryfikacyjną.
- Otwórz link w wiadomości weryfikacyjnej (sprawdź też spam).
- Ponów to samo wywołanie. Nie potrzebujesz nowego klucza.
Konsola działa również przy zablokowanym API, więc nadal możesz przeglądać historię.
Zwykłe 403 z komunikatem This account cannot call the API right now oznacza ograniczenie konta. Skontaktuj się z pomocą techniczną i podaj request_id.
Saldo i limity wydatków
code | HTTP | Znaczenie | Co zrobić |
|---|---|---|---|
40201 | 402 | Saldo nie pokrywa szacowanego kosztu | Zasil konto w sekcji Rozliczenia w konsoli — zobacz Konfiguracja konta. Sprawdź GET /api/v1/chat/credit |
40202 | 402 | Osiągnięto limit wydatków | Odczytaj msg, aby ustalić który. Limit klucza (This key has reached its …): podnieś go na stronie kluczy API lub poczekaj na odnowienie. Limit zespołu (Workspace … budget has been reached): poproś właściciela, administratora lub osobę zarządzającą budżetem o podniesienie go na stronie zespołu. Limit platformy (Platform quota for today is exhausted…): nie możesz go zmienić; spróbuj po 00:00 UTC. Limity dzienne odnawiają się o 00:00 UTC, a miesięczne pierwszego dnia miesiąca o 00:00 UTC; łączny limit klucza nigdy się nie odnawia |
Nie warto ponawiać żadnego z tych błędów — saldo i limity nie zmieniają się od ponownego zapytania. Zobacz Limity wydatków.
Uprawnienia
code | HTTP | Znaczenie | Co zrobić |
|---|---|---|---|
40301 | 403 | Ten klucz nie może wywołać tego modelu | Rozszerz listę dozwolonych modeli klucza w konsoli |
40302 | 403 | Adres źródłowy nie znajduje się na liście dozwolonych IP klucza | Dodaj go lub usuń ograniczenie IP z klucza |
40303 | 403 | Usługa nie jest dostępna w tym regionie | Nie można tego obejść. Zobacz obsługiwane regiony |
40310 | 403 | Adres e-mail konta nie jest jeszcze zweryfikowany, więc konto nie może uruchamiać modeli | Otwórz link z wiadomości wysłanej przy rejestracji lub wyślij nową wiadomość z konsoli. Klucz działa po zweryfikowaniu adresu bez żadnej zmiany po Twojej stronie |
Żaden z tych błędów nie kwalifikuje się do ponowienia.
Kody powodów biblioteki zasobów
Błędy na endpointach biblioteki zasobów (/api/v1/assets…) mogą dodać do zwykłej koperty jedno odczytywalne maszynowo pole, data.reason:
{ "code": 400, "msg": "…", "data": { "reason": "asset_library_locked" }, "request_id": "req_…" }code, status HTTP, msg i errorCode zadania są takie same z powodem i bez niego, więc dotychczasowa obsługa dalej działa. Powód pojawia się tylko na endpointach biblioteki zasobów, nigdy na innych endpointach natywnych, a warstwa zgodna z OpenAI i inne warstwy zgodności zachowują własne kształty błędów. Lista może się wydłużać: nieznaną wartość traktuj jak brak powodu.
data.reason | HTTP | Znaczenie | Co zrobić |
|---|---|---|---|
asset_library_locked | 400 | Biblioteka zasobów otwiera się po pierwszym doładowaniu | Doładuj konto. Już zapisane zasoby nadal można wykorzystywać |
asset_library_full | 400 | Limit miejsca (liczba lub bajty) jest pełny | Usuń zasoby albo przejdź na wyższy plan; kolejny pokazuje GET /api/v1/assets/usage |
asset_file_too_large | 400 | Plik przekracza limit planu na plik | Użyj mniejszego pliku albo przejdź na wyższy plan |
asset_daily_limit | 429 | Osiągnięto dzienną liczbę nowych zasobów | Poczekaj do 00:00 UTC i zastosuj się do Retry-After |
asset_storage_full | 503 | Miejsce na całej platformie jest wyczerpane | Spróbuj ponownie później |
asset_permission_denied | 403 | Twoja rola w zespole nie pozwala na tę zmianę | Poproś właściciela lub administratora zespołu albo użyj klucza, którego twórca ma uprawnioną rolę |
asset_workspace_forbidden | 403 | Ten klucz nie sięga do tej biblioteki, na przykład dlatego, że członek, który utworzył klucz zespołowy, opuścił zespół | Utwórz nowy klucz w potrzebnej przestrzeni roboczej |
Dostępność modeli
code | HTTP | Znaczenie | Ponowić? |
|---|---|---|---|
50301 | 503 | Model jest obecnie niedostępny | Tak. Spróbuj później lub wybierz inny model |
50302 | 503 | Żądanie tekstowe (strumieniowe lub zwykłe) nie powiodło się w modelu przed dostarczeniem odpowiedzi; rezerwację zwrócono | Tak. Wyślij ponownie z nowym Idempotency-Key |
40004 | 400 | Model działa, lecz nie z tymi ustawieniami | Nie. Zmień ustawienie wskazane w msg |
50301 oznacza, że model nie mógł przyjąć zadania w chwili wysyłania: nie znajduje się w ofercie, nie ma aktywnego cennika lub obecnie nie ma dostępnej mocy generowania. Przejściowe problemy z mocą mogą ustąpić przy ograniczonej liczbie ponowień. Niedostępnego modelu lub braku aktywnej ceny nie naprawią kolejne żądania. Odśwież katalog i uzyskaj potwierdzenie użytkownika przed zmianą modelu.
Celowo rozróżniamy 40004 i 50301. 50301 oznacza, że model obecnie nie może przyjąć żadnego zadania — oczekiwanie może pomóc. 40004 oznacza, że model działa, lecz nie obsługuje tego zestawu ustawień, a msg wskazuje ustawienie w nawiasie, na przykład This model cannot serve this parameter combination right now (…). Czekanie nie pomoże; zmiana jednej wartości tak. Żaden z tych błędów nie tworzy zadania ani nie rezerwuje środków.
Niepowodzenia po przyjęciu zadania mają state: failed
Niepowodzenie generowania po zwróceniu ID zadania przez createTask pojawia się w recordInfo jako state: "failed" z errorCode / errorMessage; rezerwacja zwykle jest zwalniana w całości, z wyjątkiem określonych odmów dotyczących treści, które strona modelu wyraźnie wskazuje jako płatne. Sytuacja odpowiadająca 40004, lecz wykryta dopiero po przyjęciu, daje errorCode: "unsupported_combination". Nie dodawaj warunków dla synchronicznych kodów biznesowych, których nie ma na tej stronie.
Kody niepowodzenia zadania
Niepowodzenie po przyjęciu zadania trafia do errorCode w recordInfo. Zbiór jest zamknięty: diagnostyka wewnętrzna nigdy nie trafia do tego pola, więc wartość spoza listy obsłuż jak upstream_failed.
errorCode | Znaczenie | Ponowić? |
|---|---|---|
invalid_request | Parametry nie zostały przyjęte | Nie. Popraw je i wyślij ponownie |
unsupported_combination | Ten zestaw ustawień nie jest obecnie obsługiwany | Nie. Wybierz inne ustawienia |
content_rejected | Wybrany model odmówił obsługi treści. Sam SpicyAPI nie kontroluje żądań — zobacz Odpowiedzialność za treści i korzystanie | Nie. Zmień prompt lub wybierz inny model |
rate_limited | Usługa generowania była zajęta | Tak. Odczekaj i ponów |
upstream_unavailable | Generowanie jest chwilowo niedostępne | Tak. Spróbuj za chwilę |
invalid_asset | Materiał referencyjny nie jest już dostępny | Nie przez ponowienie zadania. retry wykorzystuje pierwotne dane wejściowe, więc prześlij materiał ponownie i utwórz nowe zadanie z nowym URI |
generation_failed | Generowanie rozpoczęło się, lecz nie dało żadnego wyniku | Być może. Te same dane wejściowe zwykle prowadzą do tego samego niepowodzenia |
timeout | Zadanie nie zakończyło się przed terminem | Być może. Spróbuj krótszego czasu lub niższej rozdzielczości |
upstream_failed | Ogólny kod dla pozostałych przypadków | Tak. Odczekaj i ponów |
Zadania zakończone niepowodzeniem lub wygaśnięciem zwykle zwalniają całą rezerwację. Niektóre odmowy dotyczące treści są rozliczane według przyjętej wyceny, ale tylko wtedy, gdy strona modelu wyraźnie podaje ten wyjątek. Ostateczną opłatę sprawdzisz w settled i cost. errorMessage jest dla ludzi: opisuje powód, może przytoczyć własny powód modelu po angielsku (zobacz wyżej), a jego brzmienie się zmienia — logika powinna korzystać wyłącznie z errorCode. „Ponowienie” w tej tabeli oznacza wysłanie nowego zadania, które po powodzeniu jest rozliczane ponownie.
Które błędy powodują naliczenie opłaty
Stan zadania i stan połączenia to dwie różne informacje.
Jednoznacznie odrzucone utworzenia zadań, zadania wygasłe i historyczne rekordy anulowania nie powodują opłaty za generowanie. Zadania zakończone niepowodzeniem są zwykle bezpłatne, z wyjątkiem określonych odmów dotyczących treści wyraźnie wskazanych jako płatne na stronie modelu. Przekroczenie czasu lub rozłączenie po stronie klienta nie oznacza niepowodzenia zadania. Przyjętych zadań nie można anulować: odzyskaj pierwotne zadanie i sprawdź jego stan końcowy oraz rozliczenie. Rozłączony strumień tekstowy może być rozliczony, gdy zapisano już prawidłowe wykorzystanie.
| Sytuacja | Opłata? |
|---|---|
API jednoznacznie odrzuca createTask bez utworzenia zadania (na przykład 40201, 40202, 40004 lub 50301) | Nie. Utworzenie zadania i rezerwacja są jedną transakcją; błąd bramy lub brak odpowiedzi nie dowodzi odrzucenia |
| Wysyłanie przekracza czas, zamykasz kartę lub połączenie się urywa | Jeszcze nie wiadomo. Odzyskaj informację o przyjęciu z pierwotnym kluczem idempotencji; nie traktuj tego jako bezpłatnego ponowienia |
createTask zwraca 202, a zadanie później ma failed / expired | Zwykle bez opłaty. Określone wyniki failed / content_rejected są rozliczane według przyjętej wyceny, jeżeli strona modelu wyraźnie to podaje. Sprawdź settled i cost |
Historyczne zadanie ma canceled | Nie. To stan zgodności z historią, nie obecnie dostępna operacja |
createTask zwraca 202, zadanie ma succeeded | Tak, według rzeczywistego wykorzystania, lecz najwyżej do przyjętej rezerwacji. Niewykorzystane środki są zwalniane; później nie pobieramy kwoty ponad rezerwację |
Dowolny błąd z recordInfo, chat/credit lub common/* | Nie. Te endpointy nigdy nie są płatne |
Ograniczenie częstotliwości (429) lub deduplikacja przez klucz idempotencji | Nie. Pierwsze żądanie nie zostało przyjęte; drugie zwraca pierwotne zadanie rozliczane raz |
Rozliczenie lub zwolnienie środków kończy się automatycznie, także po przerwaniu procesu. Zwykłe niepowodzenia zwalniają całą rezerwację; określone odmowy dotyczące treści, wyraźnie opisane na stronie modelu, są rozliczane według przyjętej wyceny. Sprawdź rozliczenie zadania i saldo, aby potwierdzić wynik — zobacz Rozliczenia.
Kiedy warto ponowić błąd 409
| Przypadek | Ponowić? |
|---|---|
Rzadki konflikt równoczesnych createTask z tym samym kluczem i niezmienioną treścią | Tak. Odczekaj kilkaset milisekund i ponów z tym samym kluczem. Zobacz Idempotencja |
| Ten sam klucz był już użyty dla innej treści lub innego klucza API | Nie. Zatrzymaj się i popraw powiązanie klucza; ponowienie nie zmieni konfliktu |
Obrazy inline lub zatwierdzenie przesłania nadal są przetwarzane (może wystąpić Retry-After) | Tak. Odczekaj według Retry-After albo kilka sekund i wyślij z tym samym Idempotency-Key |
| Ceny lub konfiguracja konta zmieniły się w trakcie żądania | Tak. Jeżeli korzystasz z wycen, uzyskaj nową, a następnie wyślij ponownie |
| Plik nadal jest kopiowany | Tak. Odczekaj kilka sekund i ponownie poproś o link do pobrania |
jobs/retry dla zadania, które nie zakończyło się lub nie zakończyło rozliczenia | Tak, później. Poczekaj, aż zadanie źródłowe osiągnie stan końcowy, a settled będzie true, następnie ponów |
jobs/retry dla zadania zakończonego powodzeniem lub z danymi wejściowymi już usuniętymi zgodnie z zasadami przechowywania | Nie. Utwórz nowe zadanie przez createTask |
| Adres przesyłania lub przesłany plik wygasł | Nie. Prześlij plik ponownie |
Implementacja ponowień
Nie ponawiaj bez sprawdzenia przyczyny
400, 401, 40004, 40201 i podobne błędy danych wejściowych lub uprawnień dają tę samą odpowiedź niezależnie od liczby prób. Każda próba oznaczona wyżej „Nie” jest stratą czasu — i zużywa Twój limit częstotliwości.
Stosuj wykładniczo rosnące opóźnienie z losowym rozrzutem, aby wiele nieudanych wywołań nie wracało jednocześnie:
// 409 is operation-specific and must be handled at the call site.
const RETRYABLE = new Set([429, 500, 503, 50301]);
async function callWithRetry(path, init, { attempts = 4 } = {}) {
for (let i = 0; i < attempts; i++) {
const res = await fetch(`https://api.spicyapi.ai/api/v1${path}`, init);
// A page from the edge network is not our JSON envelope
if (!res.headers.get('content-type')?.includes('json')) {
const text = await res.text();
throw new Error(`HTTP ${res.status}, not JSON: ${text.slice(0, 200)}`);
}
const body = await res.json();
if (body.code === 200) return body.data;
if (!RETRYABLE.has(body.code)) {
throw new Error(`${body.code} ${body.msg} (request_id=${body.request_id})`);
}
// When the server says how long to wait, trust it
const retryAfter = Number(res.headers.get('retry-after'));
const backoff = Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter * 1000
: 2 ** i * 1000 + Math.random() * 500;
await new Promise((r) => setTimeout(r, backoff));
}
throw new Error('out of retries');
}import random, time, requests
# 409 is operation-specific and must be handled at the call site.
RETRYABLE = {429, 500, 503, 50301}
BASE = "https://api.spicyapi.ai/api/v1"
def call_with_retry(method: str, path: str, attempts: int = 4, **kwargs) -> dict:
for i in range(attempts):
res = requests.request(method, f"{BASE}{path}", **kwargs)
# A page from the edge network is not our JSON envelope
try:
body = res.json()
except ValueError:
raise RuntimeError(f"HTTP {res.status_code}, not JSON: {res.text[:200]}")
if body["code"] == 200:
return body["data"]
if body["code"] not in RETRYABLE:
raise RuntimeError(f'{body["code"]} {body["msg"]} (request_id={body.get("request_id")})')
# When the server says how long to wait, trust it
retry_after = res.headers.get("retry-after")
backoff = float(retry_after) if retry_after else 2**i + random.random() * 0.5
time.sleep(backoff)
raise RuntimeError("out of retries")package main
import (
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"strconv"
"time"
)
// retryable lists the codes worth another attempt. Anything else returns
// the same answer however many times you ask — and each ask eats your own
// rate limit.
// Whether the generation itself failed is read from recordInfo's state.
var retryable = map[int]bool{429: true, 500: true, 503: true, 50301: true}
type envelope struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data json.RawMessage `json:"data"`
RequestID string `json:"request_id"`
}
func callWithRetry(newReq func() (*http.Request, error), attempts int, out any) error {
for i := 0; i < attempts; i++ {
req, err := newReq()
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req)
if err != nil {
// Transport-level failure: the request may well have arrived.
// On createTask, send the same Idempotency-Key or this retry
// turns into a second generation and a second charge.
time.Sleep(backoff(i, ""))
continue
}
var env envelope
decErr := json.NewDecoder(res.Body).Decode(&env)
io.Copy(io.Discard, res.Body)
res.Body.Close()
if decErr != nil {
// Not our JSON envelope, for example a page from the edge network.
return fmt.Errorf("HTTP %d, response is not JSON: %w", res.StatusCode, decErr)
}
if env.Code == 200 {
return json.Unmarshal(env.Data, out)
}
if !retryable[env.Code] {
return fmt.Errorf("%d %s (request_id=%s)", env.Code, env.Msg, env.RequestID)
}
// When the server says how long to wait, trust it
time.Sleep(backoff(i, res.Header.Get("Retry-After")))
}
return fmt.Errorf("out of retries")
}
// backoff trusts Retry-After when present, otherwise backs off
// exponentially with jitter so a batch of failures does not come back
// as one thundering herd.
func backoff(attempt int, retryAfter string) time.Duration {
if s, err := strconv.Atoi(retryAfter); err == nil && s > 0 {
return time.Duration(s) * time.Second
}
base := time.Duration(1<<attempt) * time.Second
return base + time.Duration(rand.Intn(500))*time.Millisecond
}Przy ponawianiu createTask zawsze wysyłaj ten sam Idempotency-Key — inaczej sytuacja „zadanie się powiodło, tylko odpowiedź zaginęła” stanie się podwójnym generowaniem i podwójną opłatą.
Diagnostyka
Gdy błąd jest niezrozumiały, sprawdź kolejno:
request_id— z tą wartością możemy znaleźć dokładne żądanie.code— sprawdź tabele wyżej.- Logi w konsoli — wklej ID zadania lub
request_idw wyszukiwarkę na stronie Logi, aby otworzyć zadanie: status, koszt, opóźnienie, klucz użyty do wysłania, kod błędu i powód. Parametry żądania i wyniki są pokazywane tylko podczas ich przechowywania, a w zespole wyłącznie rolom uprawnionym do odczytu treści.
msg dla 500 zawsze jest ogólnym komunikatem „usługa chwilowo niedostępna”, bez szczegółów wewnętrznych: struktura wewnętrzna, nazwy komponentów i ślady stosu nigdy nie pojawiają się w odpowiedzi. Możemy je ustalić wyłącznie przez request_id.
Skontaktujesz się z nami, rozpoczynając rozmowę w sekcji Pomoc w konsoli lub pisząc na support@spicyapi.ai. Podaj request_id albo ID zadania, czas (UTC) i code. Nigdy nie wysyłaj klucza API.
OpenAPI i indeks endpointów
Kontrakt OpenAPI 3.1 do pobrania, wszystkie publiczne endpointy z odpowiednimi poradnikami, wspólne nagłówki żądań i produkcyjny adres API.
Limity żądań
Jak działają osobne limity odczytu i zapisu, co zużywa każdy z nich, jakie inne ograniczenia możesz napotkać i dlaczego współbieżność to osobna kwestia.

