spicyapiDokumentation
SDKs
Hauptinhalt

PHP-SDK

Das offizielle PHP-SDK für SpicyAPI (spicyapi/spicyapi) – mit Composer installieren, die erste Generierung schätzen und ausführen, eine lokale Datei hochladen, Webhooks sicher prüfen und jeden Fehlercode behandeln.

spicyapi/spicyapi ist das offizielle PHP-SDK für SpicyAPI. Es legt die Erzeugung von Bildern, Videos und Audio hinter einen einzigen SpicyClient: Er hängt Ihren API-Schlüssel an, packt den Antwort-Envelope in schlichte assoziative Arrays aus, wartet mit begrenztem Back-off auf einen Task, lädt lokale Dateien in einem Aufruf hoch und prüft Webhook-Signaturen, ohne einen der drei Fehler zu machen, die PHP-Code an dieser Stelle üblicherweise macht.

Es setzt PHP 8.1 oder neuer sowie ext-json und ext-hash voraus und hat keine Bibliotheksabhängigkeiten. ext-curl wird genutzt, wenn es vorhanden ist; fehlt es, weicht der Client auf den Stream-Wrapper https:// aus.

Ist das das richtige Werkzeug?

Sie möchten …Verwenden Sie
SpicyAPI aus Ihrem eigenen PHP-Code aufrufenDieses SDK
Es aus Node.js oder TypeScript aufrufenDas TypeScript-SDK
Es aus Python, Go oder Java aufrufenDas Python-, Go- oder Java-SDK
Ohne Code aus einem Terminal heraus generierenDie CLI
Einen KI-Assistenten SpicyAPI für Sie aufrufen lassenDen MCP-Server und den Agent Skill
Ein Chat- oder Textmodell nutzenEinen etablierten PHP-Client für die Formate von OpenAI, Anthropic oder Gemini – siehe Text und Streaming

Installieren

composer require spicyapi/spicyapi

Legen Sie Ihren Schlüssel in die Umgebung, damit er weder in Ihrem Code noch in der Versionsverwaltung auftaucht:

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

Ihr erstes Ergebnis

Preis erfragen, entscheiden, Task anlegen, warten, Dateien lesen. Eine Kostenschätzung ist kostenlos und reserviert nichts.

use SpicyApi\SpicyClient;

$client = new SpicyClient();                 // liest SPICY_API_KEY aus der Umgebung

$quote = $client->quote('MODEL_ID_FROM_CATALOG', ['prompt' => 'a lantern in fog']);
printf("at most %s\n", $quote['maxCharge']); // entscheiden, bevor Sie Geld ausgeben

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

Jeder Aufruf gibt das dekodierte data des Antwort-Envelopes als schlichtes assoziatives Array zurück; lesen Sie es also mit $quote['maxCharge'], nicht mit $quote->maxCharge.

MODEL_ID_FROM_CATALOG ist ein Platzhalter. Holen Sie sich eine echte ID aus dem authentifizierten Katalog – $client->listModels(), also GET /api/v1/models – und nie aus dem Gedächtnis oder von einer alten Seite: Modelle kommen hinzu, werden pausiert und eingestellt. Bauen Sie die Eingabe aus dem inputSchema genau dieses Modells, das listModels() zurückgibt; jedes Modell nimmt andere Felder an.

Beträge sind Dezimalzeichenketten in US-Dollar, nie Floats, denn binäre Gleitkommazahlen können die meisten Dezimalbeträge nicht exakt speichern. Vergleichen Sie sie mit SpicyClient::compareUsd(). Credits oder Punkte gibt es in dieser API nirgends.

waitForTerminal kehrt bei jedem Endzustand zurück, und ein Endzustand ist kein erfolgreicher Zustand. Prüfen Sie den Status, bevor Sie die Ausgabe lesen, und rufen Sie bei Endpunkten, die mit Text statt mit Dateien antworten, zuerst SpicyClient::outputText($final) auf.

Die erste Abfrage überspringen

waitSeconds hält die Verbindung offen, bis der Task fertig ist; ein kurzer Auftrag braucht damit gar keine Abfrageschleife. Einen fertigen Task sagt der Parameter nicht zu. Läuft das Budget ab, bekommen Sie stattdessen die gewöhnliche Annahmeantwort – gleiche Form, anderer state. Verzweigen Sie deshalb über den Status, nie darüber, dass Sie den Parameter mitgegeben haben:

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

Mit einer lokalen Datei beginnen

Bild-zu-Video, Motivtausch und Bildbearbeitung brauchen Ihr Material zuerst auf unserer Seite. uploadFile erledigt den dreistufigen Upload – Ticket, PUT in den Speicher, Commit – in einem Aufruf und gibt eine spicy://-URI zurück. Diese URI kommt in die Eingabe.

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

Eine öffentlich erreichbare HTTPS-Adresse können Sie direkt in die Eingabe schreiben; dafür ist gar kein Upload nötig. Bilder werden bis 10 MiB angenommen, Audio und Video bis 90 MiB; siehe Medien.

Webhooks

use SpicyApi\SpicyWebhook;

// Das Geheimnis kommt zuerst. Die übrigen Argumente sind optional: $_SERVER und die rohen
// Bytes von php://input werden gelesen, sofern ein Framework sie nicht schon verbraucht hat.
$delivery = SpicyWebhook::verifyRequest(getenv('SPICY_WEBHOOK_SECRET') ?: '');

// In einem Framework, das den Body puffert, übergeben Sie die exakten Bytes selbst:
$delivery = SpicyWebhook::verifyRequest(
    secret: getenv('SPICY_WEBHOOK_SECRET') ?: '',
    server: $_SERVER,
    rawBody: $request->getContent(),
);

Drei Fehler, die PHP-Code hier macht und die diese Klasse allesamt vermeidet:

  • Den Body vor der Prüfung neu serialisieren. json_decode und danach json_encode verändert die Bytes, und die Signatur passt dann nie. Prüfen Sie die rohen Bytes.
  • Signaturen mit == oder === vergleichen. Über das Zeitverhalten hinaus vergleicht == zwei Zeichenketten, die beide wie 0e… aussehen, als Zahlen – "0e123" == "0e456" ergibt true. Diese Klasse verwendet hash_equals.
  • Die Zeitstempelprüfung auslassen. Eine gültige Signatur bleibt für immer gültig; ohne Frischefenster lässt sich eine mitgeschnittene Zustellung erneut einspielen.

Zustellungen werden wiederholt; entdoppeln Sie deshalb über die Zustellungs-ID. Das vollständige Zustellformat und den Wiederholungsplan finden Sie unter Webhooks.

Guthaben, Verbrauch und Verlauf

$balance = $client->getBalance();                        // verfügbar / reserviert / gesamt, USD-Zeichenketten
$usage = $client->getUsage('2026-09-01', '2026-09-08');  // abgerechnete Ausgaben über [from, to)

foreach ($client->iterTasks(state: 'succeeded') as $summary) {
    echo $summary['taskId'], "\n";                       // jede Seite, Filter werden wiederholt
}

held ist Geld, das von noch nicht abgerechneten Tasks reserviert ist – es ist also weder ausgegeben noch verfügbar. Datumsangaben sind UTC-Kalendertage im Format YYYY-MM-DD und bilden ein halboffenes Intervall [from, to) von höchstens 92 Tagen. Alle drei Aufrufe sehen nur den aufrufenden API-Schlüssel: Andere Schlüssel und Generierungen aus der Konsole sind nicht enthalten.

getUsage() ist für den Abgleich gedacht, nicht für den Fortschritt. Dahinter steht ein kontoweites Anfragebudget, das sich alle Schlüssel teilen, und dieser Begrenzer sperrt im Zweifelsfall: Wer ihn ständig abfragt, kann das ganze Konto von seinen eigenen Auswertungen aussperren. Verfolgen Sie einen Task stattdessen mit getTask().

listTasks() liefert eine Seite als {items, hasMore, nextCursor}. Von Hand zu blättern heißt: jeden Filter unverändert wiederholen, nur den Cursor bewegen und aufhören, sobald hasMore false oder nextCursor leer ist. Wer nur hasMore prüft, macht aus einem Aussetzer auf der Serverseite eine Endlosschleife kostenpflichtiger Aufrufe, die wie ein Hänger aussieht. iterTasks() erledigt all das.

Fehler

Fehlschläge werfen SpicyApiError mit $businessCode, $requestId und $retryAfterSeconds. Verzweigen Sie über den Business-Code, nie über die Meldung – Meldungen werden übersetzt, Codes nicht.

HTTP 503 teilen sich drei verschiedene Business-Codes, der Status allein sagt Ihnen also nicht, was zu tun ist:

CodeBedeutungWas zu tun ist
40003Hochgeladene Bytes passen nicht zum TicketMit einem frischen Ticket neu beginnen
40004Keine Bereitstellung bedient diese ParameterkombinationDen in der Meldung genannten Parameter ändern
40201Guthaben reicht nichtGuthaben aufladen
409Der Idempotenzschlüssel wurde für eine andere Anfrage wiederverwendetDie ursprüngliche Anfrage unter diesem Schlüssel senden – oder die geänderte unter einem neuen
40901Der Preis hat sich vor dem Anlegen geändertNeu schätzen, denselben Idempotenzschlüssel behalten
503Eine Abhängigkeit ist kurz nicht erreichbarGemäß Retry-After warten
50301Gerade keine nutzbare Bereitstellung und kein PreisKatalog neu laden
50302Synchrone Generierung bei der Ausführung fehlgeschlagen, erstattetMit einem neuen Idempotenzschlüssel wiederholen

$error->guidance() liefert dieselbe Empfehlung zur Laufzeit. Jeder Code steht unter Fehler.

Zwei Dinge, die Ihnen Geld sparen

Bleiben Sie pro Absendung bei einem Idempotenzschlüssel und verwenden Sie ihn bei jedem erneuten Senden wieder – eine verlorene Antwort ist kein Beweis dafür, dass der Task nicht angelegt wurde. Siehe Idempotenz.

Ein erfolgreicher Task wird berechnet, auch wenn das Ergebnis enttäuscht. quote() reserviert nichts und legt nichts an.

Was dieses Paket nicht tut

  • Textmodelle. Sie sprechen die Formate von OpenAI, Anthropic und Gemini; die etablierten PHP-Clients dafür funktionieren bereits gegen https://api.spicyapi.ai/v1. Siehe Text und Streaming.
  • Code auf der Browserseite. Der Schlüssel gehört auf Ihren Server, nie in etwas, das Sie an Besucher ausliefern.
  • Ein Modell auswählen oder seine Felder ausfüllen. Es reicht Ihnen den Katalog und das Schema jedes Modells; was Sie senden, entscheiden Sie.
  • Einen Task abbrechen. Ist ein Task erst angenommen, läuft er bis zum Ende.

Quellcode

Das Paket steht unter MIT-Lizenz und wird offen unter github.com/spicyapi-ai/spicy-php entwickelt. Sein README ist die kürzeste vollständige Führung durch den Client, und in examples/ liegen lauffähige Skripte; diese Seite ergänzt die Konzepte drumherum.

Nächste Schritte

Auf dieser Seite