spicyapiDokumentacja
Materiały referencyjne
Treść główna

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_…" }
  • code jest kodem biznesowym, nie statusem HTTP. 200 oznacza powodzenie.
  • Przy błędzie pole data jest nieobecne (nie ma wartości null) — otrzymujesz code, msg i request_id. Jedyny wyjątek: niektóre błędy biblioteki zasobów dodają data.reason.
  • msg jest 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 z code.
  • request_id jest również zwracane w nagłówku X-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:

  • msg we wspólnej strukturze odpowiedzi;
  • errorMessage zadania zakończonego niepowodzeniem, zarówno w recordInfo, jak i w callbackach webhooków (z jednym wyjątkiem opisanym niżej);
  • error.message w 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:

  1. Nagłówek żądania Accept-Language, tylko dla tego żądania. Stosujemy standardowe wagi HTTP: wybieramy rozpoznany język o najwyższym q. Warianty regionalne są sprowadzane do języka — de-DE jest traktowane jako de, a pt-PT jako pt-BR. Chiński nie jest dostępny, więc żaden jego znacznik nie pasuje i sprawdzamy kolejną opcję. * oznacza brak preferencji.
  2. 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.
  3. 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:

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

Accept-Language: ja
HTTP/1.1 402 Payment Required
Content-Language: ja

{ "code": 40201, "msg": "残高が不足しています", "request_id": "req_…" }

Kody ogólne

codeHTTPZnaczeniePonowić?
200200Powodzenie—
400400Nieprawidłowa struktura żądania lub parametryNie. Popraw i wyślij ponownie
40003400Rozmiar, typ lub zawartość przesłanego pliku nie odpowiada deklaracji podanej przy żądaniu adresu przesyłaniaNie. Poproś o nowy adres z prawidłowymi contentType i rozmiarem, prześlij ponownie i zatwierdź
40004400Żą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
401401Uwierzytelnienie nie powiodło się; msg wskazuje jeden z trzech przypadkówNie. Popraw nagłówek lub klucz
403403Brak uprawnienia. Przy wywołaniu API z prawidłowym kluczem oznacza ograniczenie konta — szczegóły niżej. Niezweryfikowany adres e-mail daje zamiast tego 40310Nie
404404Zasób nie istnieje lub nie należy do CiebieNie
409409Konflikt z bieżącym stanemCzasami — zobacz niżej
40901409Wycena wygasła, żądanie się zmieniło lub cena się zmieniłaUzyskaj i zatwierdź nową wycenę; przyjęte żądania odzyskuj z pierwotnym kluczem idempotencji
413413Treść żądania przekracza rozmiar przyjmowany przez serwerNie. Zmniejsz treść lub prześlij pliki przez proces przesyłania mediów
429429Zbyt wiele żądańZwykle. Odczekaj zgodnie z Retry-After. Jeden przypadek 429 nie dotyczy szybkości — zobacz Limity częstotliwości
500500Błąd po naszej stronieTak. Odczekaj i ponów
503503Usługa pomocnicza (wyceny, zapisywanie obrazów inline, przesyłanie mediów) jest chwilowo niedostępnaTak. 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 przyczynaCo 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ącychKlient 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 kluczemWklejono 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 expiredKlucz 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…:

  1. Otwórz konsolę. Baner u góry informuje o niezweryfikowanym adresie i pozwala ponownie wysłać wiadomość weryfikacyjną.
  2. Otwórz link w wiadomości weryfikacyjnej (sprawdź też spam).
  3. 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

codeHTTPZnaczenieCo zrobić
40201402Saldo nie pokrywa szacowanego kosztuZasil konto w sekcji Rozliczenia w konsoli — zobacz Konfiguracja konta. Sprawdź GET /api/v1/chat/credit
40202402Osiągnięto limit wydatkówOdczytaj 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

codeHTTPZnaczenieCo zrobić
40301403Ten klucz nie może wywołać tego modeluRozszerz listę dozwolonych modeli klucza w konsoli
40302403Adres źródłowy nie znajduje się na liście dozwolonych IP kluczaDodaj go lub usuń ograniczenie IP z klucza
40303403Usługa nie jest dostępna w tym regionieNie można tego obejść. Zobacz obsługiwane regiony
40310403Adres e-mail konta nie jest jeszcze zweryfikowany, więc konto nie może uruchamiać modeliOtwó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.reasonHTTPZnaczenieCo zrobić
asset_library_locked400Biblioteka zasobów otwiera się po pierwszym doładowaniuDoładuj konto. Już zapisane zasoby nadal można wykorzystywać
asset_library_full400Limit miejsca (liczba lub bajty) jest pełnyUsuń zasoby albo przejdź na wyższy plan; kolejny pokazuje GET /api/v1/assets/usage
asset_file_too_large400Plik przekracza limit planu na plikUżyj mniejszego pliku albo przejdź na wyższy plan
asset_daily_limit429Osiągnięto dzienną liczbę nowych zasobówPoczekaj do 00:00 UTC i zastosuj się do Retry-After
asset_storage_full503Miejsce na całej platformie jest wyczerpaneSpróbuj ponownie później
asset_permission_denied403Twoja 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_forbidden403Ten 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

codeHTTPZnaczeniePonowić?
50301503Model jest obecnie niedostępnyTak. Spróbuj później lub wybierz inny model
50302503Żądanie tekstowe (strumieniowe lub zwykłe) nie powiodło się w modelu przed dostarczeniem odpowiedzi; rezerwację zwróconoTak. Wyślij ponownie z nowym Idempotency-Key
40004400Model działa, lecz nie z tymi ustawieniamiNie. 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.

errorCodeZnaczeniePonowić?
invalid_requestParametry nie zostały przyjęteNie. Popraw je i wyślij ponownie
unsupported_combinationTen zestaw ustawień nie jest obecnie obsługiwanyNie. Wybierz inne ustawienia
content_rejectedWybrany model odmówił obsługi treści. Sam SpicyAPI nie kontroluje żądań — zobacz Odpowiedzialność za treści i korzystanieNie. Zmień prompt lub wybierz inny model
rate_limitedUsługa generowania była zajętaTak. Odczekaj i ponów
upstream_unavailableGenerowanie jest chwilowo niedostępneTak. Spróbuj za chwilę
invalid_assetMateriał referencyjny nie jest już dostępnyNie przez ponowienie zadania. retry wykorzystuje pierwotne dane wejściowe, więc prześlij materiał ponownie i utwórz nowe zadanie z nowym URI
generation_failedGenerowanie rozpoczęło się, lecz nie dało żadnego wynikuByć może. Te same dane wejściowe zwykle prowadzą do tego samego niepowodzenia
timeoutZadanie nie zakończyło się przed terminemByć może. Spróbuj krótszego czasu lub niższej rozdzielczości
upstream_failedOgólny kod dla pozostałych przypadkówTak. 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.

SytuacjaOpł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ę urywaJeszcze 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 / expiredZwykle 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 canceledNie. To stan zgodności z historią, nie obecnie dostępna operacja
createTask zwraca 202, zadanie ma succeededTak, 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 idempotencjiNie. 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

PrzypadekPonowić?
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 APINie. 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 żądaniaTak. Jeżeli korzystasz z wycen, uzyskaj nową, a następnie wyślij ponownie
Plik nadal jest kopiowanyTak. 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 rozliczeniaTak, 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 przechowywaniaNie. 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:

JavaScript
// 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');
}
Python
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")
Go
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:

  1. request_id — z tą wartością możemy znaleźć dokładne żądanie.
  2. code — sprawdź tabele wyżej.
  3. Logi w konsoli — wklej ID zadania lub request_id w 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.

Na tej stronie