spicyapiDokumentacja
SDK
Treść główna

SDK dla PHP

Oficjalne SDK SpicyAPI dla PHP (spicyapi/spicyapi): instalacja przez Composera, wycena i pierwsza generacja, wysyłanie plików z dysku, bezpieczna weryfikacja webhooków i obsługa wszystkich kodów błędów.

spicyapi/spicyapi to oficjalne SDK SpicyAPI dla PHP. Generowanie obrazów, wideo i dźwięku chowa za jednym SpicyClient: dołącza twój klucz API, rozpakowuje strukturę odpowiedzi do zwykłych tablic asocjacyjnych, czeka na zadanie z ograniczonym wydłużaniem odstępów, wysyła pliki z dysku jednym wywołaniem i weryfikuje podpisy webhooków, nie popełniając przy tym żadnego z trzech błędów, które kod w PHP popełnia w tym miejscu najczęściej.

Wymaga PHP 8.1 lub nowszego oraz rozszerzeń ext-json i ext-hash, a nie ma żadnych zależności bibliotecznych. Z ext-curl korzysta, jeśli jest dostępne; bez niego klient sięga po opakowanie strumieniowe https://.

Czy to właściwe narzędzie?

Chcesz…Użyj
Wywoływać SpicyAPI z własnego kodu w PHPTego SDK
Wywoływać je z Node.js albo TypeScriptuSDK dla TypeScriptu
Wywoływać je z Pythona, Go albo JavySDK dla Pythona, SDK dla Go albo SDK dla Javy
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 PHP dla formatów OpenAI, Anthropic albo Gemini – zobacz Tekst i streaming

Instalacja

composer require spicyapi/spicyapi

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 pliki. Wycena jest bezpłatna i niczego nie blokuje.

use SpicyApi\SpicyClient;

$client = new SpicyClient();                 // odczytuje SPICY_API_KEY ze zmiennej środowiskowej

$quote = $client->quote('MODEL_ID_FROM_CATALOG', ['prompt' => 'a lantern in fog']);
printf("at most %s\n", $quote['maxCharge']); // zdecyduj, zanim wydasz pieniądze

$task = $client->createTask(
    model: $quote['model'],
    input: ['prompt' => 'a lantern in fog'],
    idempotencyKey: SpicyClient::idempotencyKey(),
    quoteId: $quote['quoteId'],
    expectedCost: $quote['estimatedCost'],
);

$final = $client->waitForTerminal($task['taskId']);
foreach (SpicyClient::readyAssets($final) as $asset) {
    echo $asset['url'], "\n";
}

Każde wywołanie zwraca zdekodowane data ze struktury odpowiedzi jako zwykłą tablicę asocjacyjną, więc czytaj je przez $quote['maxCharge'], a nie $quote->maxCharge.

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(); każdy model przyjmuje inne pola.

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. Porównuj je przez SpicyClient::compareUsd(). W tym API nigdzie nie ma kredytów ani punktów.

waitForTerminal kończy się na każdym stanie końcowym, a stan końcowy to jeszcze nie stan udany. Zanim sięgniesz po wynik, sprawdź stan, a przy endpointach, które odpowiadają tekstem zamiast plikami, najpierw czytaj SpicyClient::outputText($final).

Pomiń pierwsze odpytanie

waitSeconds trzyma połączenie otwarte aż do końca zadania, więc krótka praca w ogóle nie potrzebuje pętli odpytującej. Nie obiecuje jednak zakończonego zadania. Gdy budżet czasu się wyczerpie, dostaniesz zwykłą odpowiedź o przyjęciu zadania — ten sam kształt, inny state — więc rozgałęziaj logikę po stanie, nigdy po samym fakcie, że przekazałeś ten parametr:

$submitted = $client->createTask(
    model: 'MODEL_ID_FROM_CATALOG',
    input: ['prompt' => 'a lantern in fog'],
    idempotencyKey: SpicyClient::idempotencyKey(),
    waitSeconds: 30,
);

if (!SpicyClient::isTerminal($submitted)) {
    $submitted = $client->waitForTerminal($submitted['taskId']);
}

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 wysyłkę w trzech krokach — pozwolenie na wysyłkę, PUT do magazynu, commit — w jednym wywołaniu i zwraca URI spicy://. To właśnie ten URI trafia do danych wejściowych.

$uploaded = $client->uploadFile('/path/to/reference.png');
$task = $client->createTask(
    model: 'MODEL_ID_FROM_CATALOG',
    input: ['image' => $uploaded['uri'], 'prompt' => 'slow dolly in'],
    idempotencyKey: SpicyClient::idempotencyKey(),
);

Publicznie dostępny adres HTTPS możesz podać w danych wejściowych wprost i wtedy nie musisz niczego wysyłać. Limit dla obrazów to 10 MiB, dla dźwięku i wideo – 90 MiB; zobacz Media.

Webhooki

use SpicyApi\SpicyWebhook;

// Najpierw sekret. Pozostałe argumenty są opcjonalne: $_SERVER i surowe bajty
// z php://input zostaną odczytane za ciebie, chyba że framework już je pochłonął.
$delivery = SpicyWebhook::verifyRequest(getenv('SPICY_WEBHOOK_SECRET') ?: '');

// We frameworku, który buforuje treść żądania, sam podaj dokładne bajty:
$delivery = SpicyWebhook::verifyRequest(
    secret: getenv('SPICY_WEBHOOK_SECRET') ?: '',
    server: $_SERVER,
    rawBody: $request->getContent(),
);

Trzy sposoby, na jakie kod w PHP zwykle to psuje – a których ta klasa unika:

  • Ponowna serializacja treści przed weryfikacją. json_decode, a po nim json_encode, zmienia bajty i podpis już nigdy się nie zgodzi. Weryfikuj surowe bajty.
  • Porównywanie podpisów przez == albo ===. Poza kwestią czasu wykonania == na dwóch łańcuchach, które oba wyglądają jak 0e…, porównuje je jak liczby — "0e123" == "0e456" daje true. Tutaj używane jest hash_equals.
  • Pominięcie sprawdzenia znacznika czasu. Prawidłowy podpis zostaje prawidłowy na zawsze; bez okna świeżości przechwycone dostarczenie da się odtworzyć.

Dostarczenia są ponawiane, więc odsiewaj duplikaty po identyfikatorze dostarczenia. Pełny format dostarczenia i harmonogram ponowień opisuje strona Webhooki.

Saldo, zużycie i historia

$balance = $client->getBalance();                        // available / held / total, kwoty w USD jako tekst
$usage = $client->getUsage('2026-09-01', '2026-09-08');  // rozliczone wydatki w przedziale [from, to)

foreach ($client->iterTasks(state: 'succeeded') as $summary) {
    echo $summary['taskId'], "\n";                       // wszystkie strony, filtry powtarzane za ciebie
}

held to pieniądze zablokowane przez zadania, które jeszcze się nie rozliczyły, więc nie są ani wydane, ani dostępne. Daty to dni kalendarzowe UTC w formacie YYYY-MM-DD, tworzące przedział półotwarty [from, to) obejmujący najwyżej 92 dni. Wszystkie trzy wywołania widzą wyłącznie klucz API, którym wywołujesz: bratnie klucze ani generacje z konsoli się w nich nie mieszczą.

getUsage() służy do uzgadniania rachunków, nie do śledzenia postępu. Niesie ze sobą budżet zapytań obowiązujący dla całego konta, wspólny dla wszystkich kluczy, a ten ogranicznik przy problemach domyka się zamiast otwierać — odpytywanie go potrafi odciąć całe konto od jego własnych raportów. Za pojedynczym zadaniem podążaj przez getTask().

listTasks() zwraca jedną stronę jako {items, hasMore, nextCursor}. Stronicowanie ręczne oznacza powtarzanie wszystkich filtrów bez zmian i przesuwanie wyłącznie kursora, a także zatrzymanie się, gdy hasMore ma wartość false albo nextCursor jest pusty — sprawdzanie samego hasMore zamienia usterkę po stronie serwera w nieskończoną pętlę płatnych wywołań, która wygląda jak zawieszenie. iterTasks() robi to wszystko za ciebie.

Błędy

Niepowodzenia rzucają SpicyApiError z polami $businessCode, $requestId i $retryAfterSeconds. Rozgałęziaj logikę po kodzie aplikacyjnym, nigdy po komunikacie — komunikaty są tłumaczone, kody nie.

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
40004żadne wdrożenie nie obsługuje tej kombinacji parametrówzmień parametr wskazany w komunikacie
40201za niskie saldodoładuj konto
409ten klucz idempotentności został użyty do innego żądaniawyślij pod tym kluczem pierwotne żądanie albo zmienione pod nowym kluczem
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 cenyodśwież katalog
50302generacja synchroniczna nie powiodła się podczas przetwarzania i została zwróconaponów z nowym kluczem idempotentności

$error->guidance() zwraca tę samą wskazówkę w czasie działania programu. Wszystkie kody wymienia strona Błędy.

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

Miej jeden klucz idempotentności na jedno zgłoszenie i używaj go przy każdym ponownym wysłaniu — utracona odpowiedź nie dowodzi, że zadanie nie powstało. Zobacz Idempotentność.

Za udane zadanie płacisz, nawet jeśli wynik cię rozczaruje. quote() niczego nie blokuje i niczego nie tworzy.

Czego ten pakiet nie robi

  • Modeli tekstowych. Mówią formatami OpenAI, Anthropic i Gemini; uznani klienci PHP dla tych formatów działają z https://api.spicyapi.ai/v1 od ręki. Zobacz Tekst i streaming.
  • Kodu po stronie przeglądarki. Klucz należy do twojego serwera, nigdy do czegokolwiek, co wysyłasz odwiedzającemu.
  • 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

Pakiet jest na licencji MIT i powstaje otwarcie pod adresem github.com/spicyapi-ai/spicy-php. Jego README to najkrótsza kompletna wycieczka po kliencie, a w katalogu examples/ leżą gotowe do uruchomienia skrypty; ta strona dokłada do tego pojęcia, które go otaczają.

Co dalej

Na tej stronie