spicyapiDocumentazione
SDK
Contenuto principale

SDK PHP

L’SDK PHP ufficiale di SpicyAPI (spicyapi/spicyapi) — installalo con Composer, chiedi un preventivo, esegui la tua prima generazione, carica un file locale, verifica i webhook in sicurezza e gestisci ogni codice di errore.

spicyapi/spicyapi è l’SDK PHP ufficiale di SpicyAPI. Mette la generazione di immagini, video e audio dietro un unico SpicyClient: allega la tua chiave API, apre la struttura della risposta in semplici array associativi, attende un’attività con un backoff limitato, carica i file locali con una sola chiamata e verifica le firme dei webhook senza incorrere in nessuno dei tre errori che il codice PHP commette di solito in questo punto.

Richiede PHP 8.1 o successivo più ext-json ed ext-hash, e non ha dipendenze da altre librerie. Usa ext-curl quando è presente; in sua assenza il client ripiega sullo stream wrapper https://.

È lo strumento giusto?

Vuoi…Usa
Chiamare SpicyAPI dal tuo codice PHPQuesto SDK
Chiamarlo da Node.js o TypeScriptL’SDK TypeScript
Chiamarlo da Python, Go o JavaL’SDK Python, Go o Java
Generare contenuti da un terminale, senza scrivere codiceLa CLI
Far chiamare SpicyAPI al posto tuo da un assistente IAIl server MCP e l’Agent Skill
Usare un modello di chat o di testoUn client PHP affermato per i formati OpenAI, Anthropic o Gemini: vedi Testo e streaming

Installalo

composer require spicyapi/spicyapi

Metti la chiave nell’ambiente, così non comparirà mai nel tuo codice né nel controllo di versione:

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

Il tuo primo risultato

Chiedi il prezzo, decidi, crea l’attività, aspetta, leggi i file. Il preventivo è gratuito e non accantona nulla.

use SpicyApi\SpicyClient;

$client = new SpicyClient();                 // legge SPICY_API_KEY dall'ambiente

$quote = $client->quote('MODEL_ID_FROM_CATALOG', ['prompt' => 'a lantern in fog']);
printf("at most %s\n", $quote['maxCharge']); // decidi prima di spendere

$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";
}

Ogni chiamata restituisce il campo data della struttura della risposta già decodificato, come semplice array associativo: quindi leggilo con $quote['maxCharge'], non con $quote->maxCharge.

MODEL_ID_FROM_CATALOG è un segnaposto. Prendi un ID reale dal catalogo autenticato — $client->listModels(), cioè GET /api/v1/models — e mai a memoria o da una pagina vecchia: i modelli vengono aggiunti, sospesi e ritirati. Costruisci l’input a partire dall’inputSchema di quel modello, che listModels() restituisce; ogni modello accetta campi diversi.

Gli importi sono stringhe decimali in dollari USA, mai numeri a virgola mobile, perché i float binari non riescono a contenere esattamente la maggior parte degli importi decimali. Confrontali con SpicyClient::compareUsd(). In questa API non esistono crediti né punti.

waitForTerminal ritorna a qualsiasi stato terminale, e uno stato terminale non è uno stato riuscito. Controlla lo stato prima di leggere l’output, e leggi prima SpicyClient::outputText($final) per gli endpoint che rispondono con del testo invece che con dei file.

Salta il primo polling

waitSeconds tiene aperta la connessione finché l’attività non è finita, così un lavoro breve non richiede alcun ciclo di polling. Non promette però un’attività conclusa. Se il budget si esaurisce, ricevi la normale risposta di accettazione — stessa forma, state diverso — quindi ramifica sullo stato, mai sul fatto di aver passato il parametro:

$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']);
}

Parti da un file locale

Da immagine a video, sostituzione del soggetto e modifica delle immagini hanno tutte bisogno che il tuo materiale arrivi prima dalla nostra parte. uploadFile esegue il caricamento in tre passaggi — ticket, PUT allo storage, commit — con una sola chiamata e restituisce un URI spicy://. È quell’URI che va nell’input.

$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(),
);

Un URL HTTPS raggiungibile pubblicamente può entrare direttamente nell’input e non richiede alcun caricamento. Le immagini sono accettate fino a 10 MiB, audio e video fino a 90 MiB; vedi Media.

Webhook

use SpicyApi\SpicyWebhook;

// Il segreto viene per primo. Gli altri argomenti sono facoltativi: $_SERVER e i byte
// grezzi di php://input vengono letti per te, a meno che un framework non li abbia gia' consumati.
$delivery = SpicyWebhook::verifyRequest(getenv('SPICY_WEBHOOK_SECRET') ?: '');

// Dentro un framework che bufferizza il corpo, passagli tu stesso i byte esatti:
$delivery = SpicyWebhook::verifyRequest(
    secret: getenv('SPICY_WEBHOOK_SECRET') ?: '',
    server: $_SERVER,
    rawBody: $request->getContent(),
);

Ci sono tre modi in cui il codice PHP sbaglia questo passaggio, e questa classe li evita tutti:

  • Riserializzare il corpo prima di verificarlo. Un json_decode seguito da json_encode modifica i byte, e la firma non corrisponderà mai. Verifica i byte grezzi.
  • Confrontare le firme con == o ===. Al di là dei tempi di esecuzione, == su due stringhe che assomigliano entrambe a 0e… le confronta come numeri: "0e123" == "0e456" è true. Qui si usa hash_equals.
  • Saltare il controllo del timestamp. Una firma valida resta valida per sempre; senza una finestra di validità, una consegna intercettata può essere riprodotta.

Le consegne vengono ritentate, quindi deduplica sull’ID della consegna. Il formato completo delle consegne e il calendario dei nuovi tentativi sono in Webhook.

Saldo, consumi e storico

$balance = $client->getBalance();                        // disponibile / accantonato / totale, stringhe in USD
$usage = $client->getUsage('2026-09-01', '2026-09-08');  // spesa liquidata nell'intervallo [from, to)

foreach ($client->iterTasks(state: 'succeeded') as $summary) {
    echo $summary['taskId'], "\n";                       // ogni pagina, con i filtri ripetuti per te
}

held è il denaro accantonato da attività non ancora liquidate, quindi non è né speso né disponibile. Le date sono giorni di calendario UTC, YYYY-MM-DD, e formano un intervallo semiaperto [from, to) di al massimo 92 giorni. Tutte e tre le chiamate vedono soltanto la chiave API che chiama: le altre chiavi e le generazioni della console non sono incluse.

getUsage() serve alla riconciliazione, non a seguire l’avanzamento. Porta con sé un budget di frequenza valido per tutto l’account e condiviso da ogni chiave, e quel limitatore si chiude in caso di dubbio: interrogarlo di continuo può escludere l’intero account dai propri dati. Segui invece un’attività con getTask().

listTasks() restituisce una pagina come {items, hasMore, nextCursor}. Impaginare a mano significa ripetere ogni filtro senza cambiarlo, spostare soltanto il cursore e fermarsi quando hasMore è falso oppure nextCursor è vuoto: controllare solo hasMore trasforma un intoppo lato server in un ciclo infinito di chiamate a pagamento che sembra un blocco. iterTasks() fa tutto questo per te.

Errori

I fallimenti lanciano SpicyApiError, che porta con sé $businessCode, $requestId e $retryAfterSeconds. Ramifica sul codice applicativo, mai sul messaggio: i messaggi vengono tradotti, i codici no.

L’HTTP 503 è condiviso da tre codici applicativi diversi, quindi il solo stato non può dirti che cosa fare:

CodiceChe cosa significaChe cosa fare
40003i byte caricati non corrispondono al loro ticketricomincia da un ticket nuovo
40004nessuna istanza disponibile serve quella combinazione di parametricambia il parametro indicato nel messaggio
40201saldo insufficienteaggiungi fondi
409la chiave di idempotenza è stata riusata per una richiesta diversareinvia la richiesta originale con quella chiave, oppure quella modificata con una chiave nuova
40901il prezzo è cambiato prima che l’attività venisse creatachiedi di nuovo il preventivo, mantenendo la stessa chiave di idempotenza
503una dipendenza è momentaneamente non disponibileaspetta il tempo indicato da Retry-After
50301al momento non c’è né un’istanza utilizzabile né un prezzoaggiorna il catalogo
50302una generazione sincrona è fallita a monte ed è stata rimborsatariprova con una nuova chiave di idempotenza

$error->guidance() restituisce lo stesso consiglio a runtime. Tutti i codici sono elencati in Errori.

Due cose che ti faranno risparmiare

Tieni una sola chiave di idempotenza per ogni invio e riusala a ogni reinvio: una risposta persa non dimostra che l’attività non sia stata creata. Vedi Idempotenza.

Un’attività che riesce viene addebitata, anche se il risultato delude. quote() non accantona nulla e non crea nulla.

Che cosa questo pacchetto non fa

  • I modelli di testo. Parlano i formati OpenAI, Anthropic e Gemini; i client PHP affermati per quei formati funzionano già su https://api.spicyapi.ai/v1. Vedi Testo e streaming.
  • Il codice lato browser. La chiave sta sul tuo server, mai in qualcosa che spedisci a chi visita il sito.
  • Scegliere un modello o compilarne i campi. Ti consegna il catalogo e lo schema di ogni modello; che cosa inviare lo decidi tu.
  • Annullare un’attività. Una volta accettata, un’attività arriva fino in fondo.

Codice sorgente

Il pacchetto ha licenza MIT ed è sviluppato in modo aperto su github.com/spicyapi-ai/spicy-php. Il suo README è il giro più breve e completo del client, e examples/ contiene script pronti da eseguire; questa pagina aggiunge i concetti che ci stanno intorno.

Prossimi passi

In questa pagina