spicyapiドキュメント
SDK
本文

PHP SDK

SpicyAPI 公式の PHP SDK(spicyapi/spicyapi)のガイドです。Composer での導入、見積もりと最初の生成、ローカルファイルのアップロード、安全な Webhook 検証、すべてのエラーコードの扱い方までをまとめています。

spicyapi/spicyapi は、SpicyAPI 公式の PHP SDK です。画像・動画・音声の生成を、1 つの SpicyClient の後ろにまとめます。API キーを付ける、応答のエンベロープをほどいてただの連想配列にする、上限付きのバックオフでタスクを待つ、ローカルファイルを 1 回の呼び出しでアップロードする、そして PHP のコードがそこで犯しがちな 3 つの間違いを 1 つも踏まずに Webhook の署名を検証する。こうした作業を、すべて引き受けます。

必要なのは PHP 8.1 以降と、ext-json、ext-hash です。ライブラリの依存はありません。 ext-curl があれば使いますが、なければ https:// のストリームラッパーにフォールバックします。

このツールで合っている?

やりたいこと使うもの
自分の PHP のコードから SpicyAPI を呼び出すこの SDK
Node.js や TypeScript から呼び出すTypeScript SDK
Python、Go、Java から呼び出すPython、Go、Java の各 SDK
コードを書かずに、ターミナルから生成するCLI
AI アシスタントに、SpicyAPI を代わりに呼び出してもらうMCP サーバーと Agent Skill
チャットモデルやテキストモデルを使うOpenAI、Anthropic、Gemini 形式に対応した、定評のある PHP 用クライアント。テキストとストリーミングを参照

インストール

composer require spicyapi/spicyapi

キーは環境変数に入れてください。そうすれば、コードにもバージョン管理にも残りません。

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

最初の結果

価格を尋ねて、判断して、タスクを作成し、待って、ファイルを読み取ります。見積もりは無料で、何も確保しません。

use SpicyApi\SpicyClient;

$client = new SpicyClient();                 // 環境変数から SPICY_API_KEY を読み取ります

$quote = $client->quote('MODEL_ID_FROM_CATALOG', ['prompt' => 'a lantern in fog']);
printf("at most %s\n", $quote['maxCharge']); // お金を使う前に判断します

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

どの呼び出しも、応答のエンベロープの data をデコードしたものを、ただの連想配列として返します。読むときは $quote->maxCharge ではなく $quote['maxCharge'] です。

MODEL_ID_FROM_CATALOG は仮の値です。実際の ID は、認証付きのカタログ($client->listModels()、つまり GET /api/v1/models)から取得してください。記憶や古い Web ページから写してはいけません。モデルは追加・一時停止・終了されるからです。入力は、そのモデル自身の inputSchema(listModels() が返します)に従って組み立ててください。受け付けるフィールドは、モデルごとに違います。

金額は米ドルの十進文字列で、浮動小数点数ではありません。2 進数の浮動小数点数では、ほとんどの十進の金額を正確に保持できないからです。比較には SpicyClient::compareUsd() を使ってください。この API には、クレジットやポイントの仕組みはどこにもありません。

waitForTerminal は終端状態のどれかに達した時点で戻りますが、終端状態は成功した状態という意味ではありません。出力を読む前に状態を確認してください。また、ファイルではなくテキストで応答するエンドポイントでは、先に SpicyClient::outputText($final) を読んでください。

最初のポーリングを省く

waitSeconds は、タスクが終わるまで接続を保ちます。短い処理なら、ポーリングのループはまったく要りません。ただし、タスクが終わっていることを約束するものではありません。 待ち時間を使い切ると、代わりにいつもの「受理された」応答が返ります。形は同じで、違うのは state だけです。ですから、パラメーターを渡したという事実ではなく、必ず状態で分岐してください。

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

ローカルのファイルから始める

画像から動画を作る、被写体を差し替える、画像を編集する。どれも、まず素材を当社側に置く必要があります。uploadFile は、チケットの発行、ストレージへの PUT、確定という 3 つのステップを 1 回の呼び出しで行い、spicy:// の URI を返します。入力に入れるのは、この URI です。

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

公開されている HTTPS の URL なら、アップロードは一切必要なく、そのまま入力に入れられます。画像は 10 MiB まで、音声と動画は 90 MiB までです。メディアを参照してください。

Webhook

use SpicyApi\SpicyWebhook;

// 最初の引数はシークレットです。ほかは省略できます。フレームワークがすでに読み取って
// いない限り、$_SERVER と php://input の生のバイト列は、こちらで読み取ります。
$delivery = SpicyWebhook::verifyRequest(getenv('SPICY_WEBHOOK_SECRET') ?: '');

// ボディをバッファリングするフレームワークの中では、正確なバイト列を自分で渡します。
$delivery = SpicyWebhook::verifyRequest(
    secret: getenv('SPICY_WEBHOOK_SECRET') ?: '',
    server: $_SERVER,
    rawBody: $request->getContent(),
);

PHP のコードがここで犯しがちな間違いが 3 つあります。このクラスは、そのいずれも避けています。

  • 検証の前にボディを組み直してしまう。 json_decode してから json_encode するとバイト列が変わり、署名は二度と一致しません。検証するのは生のバイト列です。
  • 署名を == や === で比較してしまう。 処理時間の問題に加えて、== は両方が 0e… の形をした文字列を数値として比較します。"0e123" == "0e456" は true になるのです。このクラスは hash_equals を使います。
  • タイムスタンプの確認を省いてしまう。 正しい署名は、いつまでも正しいままです。有効期間の確認がなければ、盗み取られた配信をあとから再生できてしまいます。

配信は再試行されるので、配信 ID で重複を取り除いてください。配信の形式と再試行のスケジュールは、Webhook にすべて書いてあります。

残高・利用状況・履歴

$balance = $client->getBalance();                        // available / held / total、いずれも米ドルの文字列
$usage = $client->getUsage('2026-09-01', '2026-09-08');  // [from, to) の期間に精算された支出

foreach ($client->iterTasks(state: 'succeeded') as $summary) {
    echo $summary['taskId'], "\n";                       // 全ページ。フィルターは自動で繰り返されます
}

held は、まだ精算されていないタスクが仮押さえしている金額です。使われてもいなければ、使えるわけでもありません。日付は UTC の暦日(YYYY-MM-DD)で、半開区間 [from, to) を作ります。長さは最大 92 日です。この 3 つの呼び出しはいずれも、呼び出しに使った API キーの分しか見えません。同じアカウントのほかのキーや、コンソールからの生成は含まれません。

getUsage() は突き合わせのための機能であって、進捗を見るためのものではありません。この呼び出しには、すべてのキーで共有されるアカウント単位のレート予算が付いていて、その制限はフェイルクローズで働きます。つまり、ポーリングすると、アカウント全体が自分の利用状況を読めなくなることがあります。1 件のタスクを追うには、代わりに getTask() を使ってください。

listTasks() は 1 ページ分を {items, hasMore, nextCursor} の形で返します。自分でページを送るなら、すべてのフィルターを変えずに繰り返し、動かすのはカーソルだけにして、hasMore が false になったときまたは nextCursor が空になったときに止めてください。hasMore しか見ないと、サーバー側の一時的な不具合が、課金される呼び出しの終わらないループになります。しかも、見た目はただのハングアップです。iterTasks() は、そのすべてを代わりに行います。

エラー

失敗は SpicyApiError として送出され、$businessCode、$requestId、$retryAfterSeconds を持ちます。分岐は必ずビジネスコードで行い、メッセージでは判断しないでください。メッセージは翻訳されますが、コードは変わりません。

HTTP 503 は、3 つの異なるビジネスコードで共有されています。 そのため、ステータスだけでは、次に何をすべきかは分かりません。

コード意味対処
40003アップロードされたバイト列が、チケットと一致しないチケットを取り直して、最初からやり直します
40004そのパラメーターの組み合わせに対応できる構成がないメッセージに書かれているパラメーターを変更します
40201残高が足りないチャージします
409同じ冪等キーが、別のリクエストに使い回されたそのキーでは元のリクエストを送り直すか、変更したほうを新しいキーで送ります
40901タスクが作成される前に価格が変わった見積もりを取り直します。冪等キーは同じものを使ってください
503依存している何かが、短時間だけ利用できないRetry-After の秒数だけ待ってから再試行します
50301いま使える構成も価格もないカタログを取り直します
50302同期的な生成が上流で失敗し、返金された新しい冪等キーで再試行します

$error->guidance() は、実行時にこれと同じ助言を返します。すべてのコードはエラーに一覧があります。

お金を守る 2 つのこと

1 回の送信につき、冪等キーは 1 つに保ち、送り直すときは毎回同じキーを使ってください。応答が失われたことは、タスクが作成されなかったことの証明にはなりません。冪等性を参照してください。

成功したタスクは課金されます。 結果が期待外れでも同じです。quote() は何も確保せず、何も作成しません。

このパッケージが扱わないこと

  • テキストモデル。 OpenAI、Anthropic、Gemini のリクエスト形式に対応しています。それらに対応した定評のある PHP 用クライアントが、https://api.spicyapi.ai/v1 に対してそのまま動きます。テキストとストリーミングを参照してください。
  • ブラウザー側のコード。 キーが置かれるべき場所は自分のサーバーであって、訪問者に配るものの中ではありません。
  • モデルを選んだり、そのフィールドを埋めたりすること。 SDK が渡すのは、カタログと各モデルのスキーマです。何を送るかは、あなたが決めます。
  • タスクのキャンセル。 いったん受理されたタスクは、最後まで実行されます。

ソースコード

このパッケージは MIT ライセンスで、github.com/spicyapi-ai/spicy-php で公開のまま開発しています。README は、クライアント全体をいちばん短く見渡せる資料で、examples/ にはそのまま実行できるスクリプトがあります。このページは、その周りにある考え方を補います。

次のステップ

このページの内容