spicyapiDocumentation
SDK
Contenu principal

SDK PHP

Le SDK PHP officiel de SpicyAPI (spicyapi/spicyapi) — l’installer avec Composer, obtenir un devis, lancer votre première génération, téléverser un fichier local, vérifier les webhooks sans risque et traiter chaque code d’erreur.

spicyapi/spicyapi est le SDK PHP officiel de SpicyAPI. Il place la génération d’images, de vidéos et de sons derrière un seul SpicyClient : il joint votre clé API, déballe l’enveloppe de réponse en simples tableaux associatifs, attend une tâche avec une temporisation bornée, téléverse les fichiers locaux en un seul appel et vérifie les signatures de webhook sans commettre aucune des trois erreurs que le code PHP fait habituellement à cet endroit.

Il demande PHP 8.1 ou plus récent, plus ext-json et ext-hash, et n’a aucune dépendance de bibliothèque. ext-curl est utilisée lorsqu’elle est présente ; à défaut, le client se rabat sur le gestionnaire de flux https://.

Est-ce le bon outil ?

Vous voulez…Utilisez
Appeler SpicyAPI depuis votre propre code PHPCe SDK
L’appeler depuis Node.js ou TypeScriptLe SDK TypeScript
L’appeler depuis Python, Go ou JavaLe SDK Python, Go ou Java
Générer des contenus depuis un terminal sans écrire de codeLa CLI
Laisser un assistant IA appeler SpicyAPI pour vousLe serveur MCP et l’Agent Skill
Utiliser un modèle de chat ou de texteUn client PHP établi pour les formats OpenAI, Anthropic ou Gemini ; voir Texte et streaming

L’installer

composer require spicyapi/spicyapi

Placez votre clé dans l’environnement, pour qu’elle n’apparaisse jamais dans votre code ni dans votre gestionnaire de versions :

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

Votre premier résultat

Demandez le prix, décidez, créez la tâche, attendez, lisez les fichiers. Le devis est gratuit et ne réserve rien.

use SpicyApi\SpicyClient;

$client = new SpicyClient();                 // lit SPICY_API_KEY dans l’environnement

$quote = $client->quote('MODEL_ID_FROM_CATALOG', ['prompt' => 'a lantern in fog']);
printf("at most %s\n", $quote['maxCharge']); // décidez avant de dépenser

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

Chaque appel renvoie le data décodé de l’enveloppe de réponse sous forme de simple tableau associatif : lisez-le donc avec $quote['maxCharge'], pas avec $quote->maxCharge.

MODEL_ID_FROM_CATALOG est un emplacement réservé. Prenez un vrai identifiant dans le catalogue authentifié — $client->listModels(), c’est-à-dire GET /api/v1/models — et jamais de mémoire ni d’une page ancienne : des modèles sont ajoutés, suspendus et retirés. Construisez l’entrée à partir de l’inputSchema propre à ce modèle, que renvoie listModels() ; chaque modèle accepte des champs différents.

Les montants sont des chaînes décimales en dollars américains, jamais des flottants, parce qu’un flottant binaire ne peut pas contenir exactement la plupart des montants décimaux. Comparez-les avec SpicyClient::compareUsd(). Il n’y a ni crédits ni points nulle part dans cette API.

waitForTerminal rend la main dès qu’un état final est atteint, et un état final n’est pas pour autant un succès. Vérifiez l’état avant de lire la sortie, et commencez par lire SpicyClient::outputText($final) pour les points de terminaison qui répondent par du texte plutôt que par des fichiers.

Sauter la première interrogation

waitSeconds garde la connexion ouverte jusqu’à la fin de la tâche : un travail court n’a donc besoin d’aucune boucle d’interrogation. Ce n’est pas la promesse d’une tâche terminée. Si le budget s’épuise, vous recevez à la place la réponse d’acceptation ordinaire — même forme, state différent : branchez donc sur l’état, jamais sur le fait d’avoir passé le paramètre.

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

Partir d’un fichier local

L’image vers vidéo, le remplacement de personne et la retouche d’image demandent tous que votre matière première soit d’abord chez nous. uploadFile réalise le téléversement en trois temps — ticket, PUT vers le stockage, validation — en un seul appel et renvoie une URI spicy://. C’est cette URI qui part dans l’entrée.

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

Une URL HTTPS publiquement joignable peut aller directement dans l’entrée, sans aucun téléversement. Les images sont acceptées jusqu’à 10 Mio, l’audio et la vidéo jusqu’à 90 Mio ; voir Médias.

Webhooks

use SpicyApi\SpicyWebhook;

// Le secret vient en premier. Les autres arguments sont facultatifs : $_SERVER et les
// octets bruts de php://input sont lus pour vous, sauf si un framework les a déjà consommés.
$delivery = SpicyWebhook::verifyRequest(getenv('SPICY_WEBHOOK_SECRET') ?: '');

// Dans un framework qui met le corps en tampon, passez-lui vous-même les octets exacts :
$delivery = SpicyWebhook::verifyRequest(
    secret: getenv('SPICY_WEBHOOK_SECRET') ?: '',
    server: $_SERVER,
    rawBody: $request->getContent(),
);

Trois façons pour du code PHP de se tromper ici, que cette classe évite toutes :

  • Sérialiser à nouveau le corps avant de le vérifier. Un json_decode suivi d’un json_encode modifie les octets, et la signature ne correspondra plus jamais. Vérifiez les octets bruts.
  • Comparer les signatures avec == ou ===. Au-delà du temps d’exécution, == entre deux chaînes qui ressemblent toutes deux à 0e… les compare comme des nombres : "0e123" == "0e456" vaut true. Ici, c’est hash_equals qui est utilisée.
  • Sauter la vérification de l’horodatage. Une signature valide le reste pour toujours ; sans fenêtre de fraîcheur, une livraison capturée peut être rejouée.

Les livraisons sont réessayées : dédoublonnez sur l’identifiant de livraison. Le format complet des livraisons et le calendrier des nouvelles tentatives figurent dans Webhooks.

Solde, consommation et historique

$balance = $client->getBalance();                        // available / held / total, chaînes USD
$usage = $client->getUsage('2026-09-01', '2026-09-08');  // dépense réglée sur [from, to)

foreach ($client->iterTasks(state: 'succeeded') as $summary) {
    echo $summary['taskId'], "\n";                       // chaque page, filtres répétés pour vous
}

held est l’argent mis de côté par des tâches qui ne sont pas encore réglées : il n’est donc ni dépensé ni disponible. Les dates sont des jours calendaires UTC, au format YYYY-MM-DD, formant un intervalle semi-ouvert [from, to) de 92 jours au maximum. Les trois appels ne voient que la clé API appelante : les clés sœurs et les générations lancées dans la console n’y figurent pas.

getUsage() sert au rapprochement comptable, pas au suivi d’avancement. Il porte un budget d’appels valable pour tout le compte et partagé par toutes les clés, et ce limiteur se ferme en cas de doute : l’interroger en boucle peut priver le compte entier de ses propres rapports. Suivez plutôt une tâche avec getTask().

listTasks() renvoie une page sous la forme {items, hasMore, nextCursor}. Paginer à la main suppose de répéter chaque filtre à l’identique et de ne faire bouger que le curseur, puis de s’arrêter quand hasMore vaut faux ou quand nextCursor est vide : ne tester que hasMore transforme un incident côté serveur en boucle sans fin d’appels payants, qui ressemble à un blocage. iterTasks() fait tout cela pour vous.

Erreurs

Les échecs lèvent une SpicyApiError qui porte $businessCode, $requestId et $retryAfterSeconds. Branchez sur le code métier, jamais sur le message : les messages sont traduits, les codes non.

Le statut HTTP 503 est partagé par trois codes métier différents : le statut seul ne peut donc pas vous dire quoi faire.

CodeSignificationQue faire
40003Les octets téléversés ne correspondent pas à leur ticketRecommencez avec un ticket neuf
40004Aucun déploiement ne sert cette combinaison de paramètresChangez le paramètre nommé dans le message
40201Solde insuffisantRechargez
409La clé d’idempotence a été réutilisée pour une autre requêteRenvoyez la requête d’origine sous cette clé, ou la requête modifiée sous une clé neuve
40901Le prix a bougé avant la création de la tâcheRedemandez un devis, en gardant la même clé d’idempotence
503Une dépendance est brièvement indisponiblePatientez selon Retry-After
50301Aucun déploiement ni prix utilisable pour l’instantActualisez le catalogue
50302Une génération synchrone a échoué en amont et a été rembourséeRéessayez avec une nouvelle clé d’idempotence

$error->guidance() renvoie les mêmes conseils à l’exécution. Tous les codes sont listés dans Erreurs.

Deux choses qui vous feront économiser

Gardez une clé d’idempotence par soumission, et réutilisez-la pour chaque renvoi : une réponse perdue ne prouve pas que la tâche n’a pas été créée. Voir Idempotence.

Une tâche qui réussit est facturée, même si le résultat déçoit. quote() ne réserve rien et ne crée rien.

Ce que ce paquet ne fait pas

  • Les modèles de texte. Ils parlent les formats réseau d’OpenAI, d’Anthropic et de Gemini ; les clients PHP établis pour ces formats fonctionnent déjà avec https://api.spicyapi.ai/v1. Voir Texte et streaming.
  • Le code côté navigateur. La clé reste sur votre serveur, jamais dans quoi que ce soit que vous envoyez à un visiteur.
  • Choisir un modèle ou remplir ses champs. Il vous fournit le catalogue et le schéma de chaque modèle ; c’est vous qui décidez quoi envoyer.
  • Annuler une tâche. Une fois acceptée, une tâche va jusqu’au bout.

Le code source

Le paquet est sous licence MIT et développé au grand jour sur github.com/spicyapi-ai/spicy-php. Son README est la visite guidée la plus courte et la plus complète du client, et examples/ contient des scripts prêts à exécuter ; cette page y ajoute les notions qui l’entourent.

Étapes suivantes

Sur cette page