spicyapiDokumentacja
SDK
Treść główna

SDK dla Javy

Oficjalne SDK SpicyAPI dla Javy (ai.spicyapi spicyapi-java, Java 17+): instalacja, wycena i pierwsza generacja, wysyłanie plików z dysku, weryfikacja callbacków i wyczerpujący switch po zapieczętowanej taksonomii błędów.

spicyapi-java to oficjalne SDK SpicyAPI dla Javy. Generowanie obrazów, wideo i dźwięku chowa za jednym SpicyClient: dołącza twój klucz API, odwzorowuje strukturę odpowiedzi na rekordy, czeka na zadanie z ograniczonym wydłużaniem odstępów, wysyła pliki z dysku jednym wywołaniem, weryfikuje podpisy callbacków i zamienia każde niepowodzenie w zapieczętowany typ wyjątku, dzięki czemu switch po całej taksonomii jest wyczerpujący już na etapie kompilacji.

Potrzebna jest Java 17 lub nowsza, a zależność jest tylko jedna: Jackson (com.fasterxml.jackson.core:jackson-databind) w wersji 2.12 lub nowszej — to wydanie, od którego rekordy dają się odwzorowywać, a typy w tym pakiecie są rekordami. Jackson jest zwykłą zależnością kompilacji, ani optional, ani wcieloną w pakiet, więc wygrywa ta wersja, którą twój projekt już ma. Na ścieżce modułów klient nazywa się ai.spicyapi, a nazwa jest przypięta w manifeście, nie wyprowadzona z nazwy pliku JAR.

Czy to właściwe narzędzie?

Chcesz…Użyj
Wywoływać SpicyAPI z własnego kodu w Javie albo innym języku JVMTego SDK
Wywoływać je z Node.js albo TypeScriptuSDK dla TypeScriptu
Wywoływać je z Pythona, Go albo PHPSDK dla Pythona, SDK dla Go albo SDK dla PHP
Generować z terminala, bez pisania koduCLI
Pozwolić, żeby asystent AI wywoływał SpicyAPI za ciebieSerwer MCP i Agent Skill
Korzystać z modelu czatu albo modelu tekstowegoUznanego klienta Javy dla formatów OpenAI, Anthropic albo Gemini – zobacz Tekst i streaming

Instalacja

Dodaj zależność z Maven Central:

<dependency>
  <groupId>ai.spicyapi</groupId>
  <artifactId>spicyapi-java</artifactId>
  <version>0.1.0</version>
</dependency>

Albo w Gradle (Kotlin DSL):

implementation("ai.spicyapi:spicyapi-java:0.1.0")

Klucz trzymaj w zmiennej środowiskowej, żeby nigdy nie pojawił się w twoim kodzie ani w systemie kontroli wersji:

export SPICY_API_KEY="sk-spicy-..."

Pierwszy wynik

Zapytaj o cenę, zdecyduj, utwórz zadanie, poczekaj, odczytaj wynik. Wycena jest bezpłatna i niczego nie blokuje. run to createTask, po którym następuje waitForTerminal.

SpicyClient client = SpicyClient.builder().apiKeyFromEnvironment().build();  // SPICY_API_KEY

CreateTaskRequest request = CreateTaskRequest.of(
        "MODEL_ID_FROM_CATALOG",
        Map.of("prompt", "a lantern in fog"));

TaskQuote quote = client.quote(request);              // bezpłatne; niczego nie blokuje
System.out.println("this will cost at most " + quote.maxCharge());

TaskRecord task = client.run(                         // createTask + waitForTerminal
        request.withQuote(quote), UUID.randomUUID().toString());

task.outputText().ifPresent(System.out::println);     // część endpointów odpowiada tekstem, nie plikami
for (TaskAsset asset : task.assets()) {
    asset.url().ifPresent(System.out::println);
}

MODEL_ID_FROM_CATALOG to symbol zastępczy. Prawdziwy identyfikator bierz z uwierzytelnionego katalogu na żywo — client.listModels(), czyli GET /api/v1/models — a nigdy z pamięci ani ze starej strony: modele są dodawane, wstrzymywane i wycofywane. Dane wejściowe buduj na podstawie inputSchema tego konkretnego modelu, które zwraca listModels(ModelFilter.all().withIncludeSchema(true)); każdy model przyjmuje inne pola.

Sprawdź outputText(), zanim przejdziesz przez assets(). Transkrypcja i podobne endpointy wkładają cały wynik w pole tekstowe i nie zwracają żadnych plików; klient, który szuka wyłącznie plików, uzna w pełni udane zadanie za takie, które niczego nie stworzyło.

Kwoty to dziesiętne łańcuchy znaków w dolarach amerykańskich, nigdy liczby zmiennoprzecinkowe, bo binarne liczby zmiennoprzecinkowe nie potrafią dokładnie zapisać większości kwot dziesiętnych. W tym API nigdzie nie ma kredytów ani punktów.

Zacznij od pliku z dysku

Generowanie wideo z obrazu, podmiana osoby i edycja obrazu wymagają, żeby twój materiał najpierw trafił na naszą stronę. uploadFile wykonuje trzy kroki w jednym wywołaniu i zwraca URI spicy://. To właśnie ten URI trafia do danych wejściowych. Publicznie dostępny adres HTTPS możesz podać wprost i wtedy nie musisz niczego wysyłać.

UploadedFile uploaded = client.uploadFile(Path.of("/path/to/reference.png"));
// uploaded.uri() -> "spicy://f/fil_..."

TaskRecord task = client.run(
        CreateTaskRequest.of("MODEL_ID_FROM_CATALOG",
                Map.of("image", uploaded.uri(), "prompt", "slow dolly in")),
        UUID.randomUUID().toString());

Te trzy kroki są też publiczne, na wypadek bajtów strumieniowanych skądinąd:

UploadTicket ticket = client.createUploadUrl(UploadContentType.IMAGE_PNG, data.length);
client.putUploadBytes(ticket, data);                  // trafia do magazynu obiektów, nie do API
UploadedFile file = client.commitUploadedFile(ticket.fileId());

Jeśli napiszesz środkowy krok samodzielnie, dwie rzeczy zawiodą nieczytelnie. Nagłówki z pozwolenia na wysyłkę są częścią tego, co podpisał wstępnie podpisany adres, więc przekazuje się je bez żadnej zmiany — pomiń jeden, zmień nazwę jednego albo usłużnie dołóż własny, a magazyn odpowie 403. I nie dołącza się tam żadnego klucza SpicyAPI: uprawnieniem jest sam adres, a prowadzi on do magazynu, nie do nas.

Limit dla obrazów to 10 MiB, dla dźwięku i wideo – 90 MiB. Klient odmawia obsługi zbyt dużego pliku lokalnie, zanim jeszcze poprosi o pozwolenie na wysyłkę. Zobacz Media.

Przechowuj wynik krócej

Wyniki i zapisany tekst żądania są przechowywane tak długo, jak ustawiono dla konta. Pojedyncze zadanie może poprosić o krócej:

TaskRecord task = client.run(request, idempotencyKey, Duration.ofHours(1));
task.retention().outputsExpireAt();                   // to, co faktycznie zastosowano

Ta wartość może tylko skracać: usługa zachowuje najmniejszą z trzech liczb — twojej, ustawienia konta i maksimum platformy — więc żadne żądanie nie odkupi sobie okna dłuższego, niż pozwala konto. Duration.ZERO to prawdziwa wartość: wyniki znikają, gdy tylko zadanie osiągnie stan końcowy. Zbyt duża wartość jest przycinana, a nie odrzucana, i właśnie dlatego to retention() na rekordzie, a nie liczba, którą wysłałeś, mówi, co faktycznie zastosowano. retryTask nie przyjmuje własnego okresu przechowywania: ponowienie dziedziczy go po zadaniu źródłowym. Zobacz Przechowywanie i usuwanie danych.

Saldo, zużycie i historia

Balance balance = client.getBalance();                          // dla całego konta
UsageReport usage = client.getUsage(                            // tylko ten klucz
        LocalDate.of(2026, 9, 13), LocalDate.of(2026, 9, 20));  // [from, to)
TaskPage page = client.listTasks(TaskFilter.all().withState(TaskState.FAILED));

Saldo jest wskaźnikiem budżetu, a nie decyzją o przyjęciu zadania. Jego część mogą stanowić środki promocyjne przypisane do konkretnych modeli — rozbicie znajdziesz w balance.fundingSources(), a puste modelSlugs przy takich środkach oznacza każdy model, a nie żaden. Dokładną odpowiedź dla jednego konkretnego żądania daje quote(), które przechodzi te same reguły przyjęcia i niczego nie blokuje.

Zużycie służy do uzgadniania rachunków, nie do śledzenia postępu. Liczy wyłącznie rozliczone opłaty, więc trwający jeszcze okres zawsze wychodzi zaniżony; spóźnione rozliczenie potrafi zmienić miniony dzień; a samo wywołanie niesie własny limit zapytań obowiązujący dla całego konta, którego ogranicznik przy problemach domyka się zamiast otwierać, więc odpytywanie go potrafi odciąć każdy klucz konta od jego własnych raportów. Za pojedynczym zadaniem podążaj przez waitForTerminal.

listTasks obejmuje wyłącznie klucz, którym wywołujesz, i zwraca same metadane. Przechodząc strony, przesuwaj kursor i nie zmieniaj niczego więcej, a przed rozpoczęciem ustaw from i to na sztywno — zostawione puste sprawiają, że usługa przy każdym żądaniu wylicza domyślne okno od nowa (to to jutro według UTC), więc przeglądanie przekraczające północ zadaje dwa różne pytania i skleja odpowiedzi w jedno. Nic tego nie zgłasza.

Callbacki

Ustaw w żądaniu callBackUrl, a rekord w stanie końcowym zostanie do ciebie dostarczony. Zweryfikuj go, zanim mu uwierzysz:

CallbackSignatureVerifier verifier =
        new CallbackSignatureVerifier(System.getenv("SPICY_WEBHOOK_SECRET"));

byte[] body = request.getInputStream().readAllBytes();   // surowe bajty, nigdy serializowane ponownie
WebhookDelivery delivery = verifier.verify(body, request::getHeader);
delivery.task().ifPresent(record -> { /* ten sam rekord, który zwraca getTask */ });

Trzy sposoby, na jakie to się psuje – a których ta klasa unika: ponowna serializacja treści przed weryfikacją (readValue, a po nim writeValueAsString, zmienia bajty i skrót już nigdy się nie zgodzi), porównywanie podpisów przez equals (kończy się na pierwszym różniącym się bajcie, co daje mierzalną różnicę czasu) oraz pominięcie sprawdzenia świeżości (prawidłowy podpis zostaje prawidłowy na zawsze, więc przechwycone dostarczenie da się odtworzyć).

Dostarczenia są ponawiane. Odsiewaj duplikaty po delivery.deliveryId(). Zobacz Webhooki.

Błędy

Każde niepowodzenie jest typu SpicyException, który jest zapieczętowany, więc switch po całej taksonomii jest wyczerpujący:

TypCo oznacza
SpicyApiExceptionusługa odpowiedziała, a odpowiedź brzmiała „nie”
SpicyProtocolExceptioncoś odpowiedziało, ale nie naszą strukturą odpowiedzi — proxy, portal przechwytujący, zły adres bazowy
SpicyTransportExceptionbrak użytecznej odpowiedzi: DNS, TLS, zerwane połączenie
SpicyTimeoutExceptionskończył się lokalny budżet czasu żądania. Wynik nieznany, a nie „nic się nie stało”
SpicyWaitTimeoutExceptionodpytywanie się poddało. Zadanie nadal trwa i nadal podlega opłacie; wyjątek niesie jego identyfikator
SpicyUploadExceptionmagazyn obiektów odrzucił wstępnie podpisane PUT
SpicyWebhookExceptionprzychodzące dostarczenie nie było autentyczne, świeże i poprawnie zbudowane

Rozgałęziaj logikę po businessCode(), nigdy po komunikacie — komunikaty to zwykły tekst, są tłumaczone i nie są częścią kontraktu. Status HTTP 503 dzielą między siebie trzy różne kody aplikacyjne, więc sam status nie powie ci, co zrobić:

KodCo to znaczyCo zrobić
40003przesłane bajty nie zgadzają się ze swoim pozwoleniem na wysyłkęzacznij od nowa, od świeżego pozwolenia; ponowny commit tego nie naprawi
40004żadne wdrożenie nie obsługuje tej kombinacji parametrówzmień parametr wskazany w komunikacie
40201za niskie saldodoładuj konto
40901cena zmieniła się, zanim powstało zadaniewyceń ponownie, zachowując ten sam klucz idempotentności
503składnik, od którego zależymy, jest chwilowo niedostępnyodczekaj tyle, ile mówi Retry-After
50301nie ma teraz działającego wdrożenia ani cenyodczekaj albo wybierz inny model
50302generacja synchroniczna nie powiodła się podczas przetwarzania i została zwróconaponów pod nowym kluczem idempotentności; pierwotny odtworzy to samo niepowodzenie

remediation() zwraca tę samą wskazówkę w czasie działania programu, a describe() układa status, kod, identyfikator żądania, komunikat i wskazówkę w jednej linii, gotowej do wpisu w logu. Kontaktując się ze wsparciem, podaj requestId(). Wszystkie kody wymienia strona Błędy.

Dwie rzeczy, które oszczędzą ci pieniędzy

Miej jeden klucz idempotentności na jedno zgłoszenie. Używaj go przy każdym ponownym wysłaniu, także po przekroczeniu czasu. Utracona odpowiedź nie dowodzi, że zadanie nie powstało — nowy klucz zamienia nieznany wynik w drugie płatne zadanie. Dlatego właśnie createTask ponawia automatycznie tylko wtedy, gdy przekazałeś klucz: bez niego ponowienie po przekroczeniu czasu byłoby drugą generacją i drugą opłatą. Zobacz Idempotentność.

Za udane zadanie płacisz, nawet jeśli wynik cię rozczaruje. quote() niczego nie blokuje i niczego nie tworzy, więc gdy cena ma znaczenie, najpierw wyceń. createTask blokuje estimatedCost i ta blokada jest sufitem: niewykorzystane środki wracają, a nic ponad tę kwotę nie zostanie pobrane później.

Czego ten pakiet nie robi

  • Modeli tekstowych. Mówią formatami OpenAI, Anthropic i Gemini — uznani klienci Javy dla tych formatów działają z https://api.spicyapi.ai/v1 od ręki, z tym samym kluczem. Zobacz Tekst i streaming.
  • Aplikacji po stronie klienta. Nigdy nie umieszczaj tego klucza w aplikacji na Androida, w pliku wykonywalnym na komputer ani w niczym innym, co użytkownik może otworzyć. Wywołuj ze swojego serwera.
  • Wyboru modelu ani wypełniania jego pól. Pakiet podaje ci katalog i schemat każdego modelu; o tym, co wysłać, decydujesz ty.
  • Anulowania zadania. Przyjęte zadanie wykonuje się do końca.

Kod źródłowy

Artefakt jest na licencji MIT i powstaje otwarcie pod adresem github.com/spicyapi-ai/spicy-java. mvn -B verify uruchamia testy jednostkowe na lokalnej atrapie usługi oraz sprawdzenie rozjazdu kontraktu: porównuje kontrakt dołączony do pakietu z opublikowanym pod adresem https://docs.spicyapi.ai/openapi.yaml, a potem jeszcze raz przechodzi każdy zbiór wartości, który pakiet ma zapisany na sztywno — bo kontrakt, któremu przybywa wartość, a klientowi nie, nigdzie się nie wywala, tylko po cichu odrzuca tę nową wartość lokalnie.

Co dalej

Na tej stronie