PHP SDK
Официальный SDK SpicyAPI для PHP (spicyapi/spicyapi): установка через Composer, расчёт стоимости и первая генерация, загрузка локального файла, безопасная проверка вебхуков и разбор всех кодов ошибок.
spicyapi/spicyapi — официальный SDK SpicyAPI для PHP. Он прячет генерацию изображений, видео и звука за одним SpicyClient: подставляет ваш API-ключ, разворачивает конверт ответа в обычные ассоциативные массивы, дожидается задачи с ограниченными сверху паузами, загружает локальные файлы одним вызовом и проверяет подписи вебхуков, не допуская ни одной из трёх ошибок, которые код на PHP обычно здесь делает.
Ему нужен PHP 8.1 или новее, а также ext-json и ext-hash, и у него нет зависимостей от библиотек. ext-curl используется, если он есть; без него клиент переходит на обёртку потоков https://.
Тот ли это инструмент?
| Вы хотите… | Что использовать |
|---|---|
| Вызывать SpicyAPI из собственного кода на PHP | Этот SDK |
| Вызывать его из Node.js или TypeScript | TypeScript SDK |
| Вызывать его из Python, Go или Java | SDK для Python, Go или Java |
| Генерировать из терминала, не написав ни строчки кода | CLI |
| Чтобы ИИ-ассистент вызывал SpicyAPI за вас | MCP-сервер и Agent Skill |
| Пользоваться чат-моделью или текстовой моделью | Проверенный клиент на PHP для форматов OpenAI, Anthropic или Gemini — см. Текст и потоковые ответы |
Установка
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 — это заглушка. Настоящий идентификатор берите из каталога, доступного по ключу, — это $client->listModels(), то есть GET /api/v1/models, — и никогда по памяти или со старой страницы: модели добавляются, приостанавливаются и снимаются с публикации. Входные данные собирайте по inputSchema самой модели: его возвращает listModels(), и каждая модель принимает свой набор полей.
Суммы передаются десятичными строками в долларах США, а не числами с плавающей точкой, потому что двоичная плавающая точка не хранит большинство десятичных сумм точно. Сравнивайте их через 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 в хранилище, commit — за один вызов и возвращает URI вида spicy://. Именно этот 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-адрес можно подставить во входные данные напрямую, загружать его никуда не нужно. Изображения принимаются до 10 MiB, аудио и видео — до 90 MiB; см. Медиа.
Вебхуки
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, и этот класс не допускает ни одного:
- Пересобрать тело запроса до проверки.
json_decode, а следомjson_encodeменяют байты, и подпись уже никогда не совпадёт. Проверяйте исходные байты. - Сравнивать подписи через
==или===. Дело не только во времени сравнения:==для двух строк, каждая из которых выглядит как0e…, сравнивает их как числа —"0e123" == "0e456"даётtrue. Здесь используетсяhash_equals. - Пропустить проверку метки времени. Верная подпись остаётся верной навсегда; без окна свежести перехваченную доставку можно воспроизвести заново.
Доставки повторяются, поэтому отсеивайте дубликаты по идентификатору доставки. Полный формат доставки и расписание повторов — на странице Вебхуки.
Баланс, расход и история
$balance = $client->getBalance(); // available / held / total, строками в USD
$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 дней. Все три вызова видят только вызывающий API-ключ: соседние ключи и генерации из консоли в них не попадают.
getUsage() нужен для сверки, а не для отслеживания хода работ. Он несёт на себе общий для всего аккаунта бюджет запросов, который делят между собой все ключи, и этот ограничитель при сбое закрывается: опрашивая его, можно лишить отчётности весь аккаунт. Следите за задачей через getTask().
listTasks() возвращает одну страницу в виде {items, hasMore, nextCursor}. Разбивать на страницы вручную — значит повторять каждый фильтр без изменений, двигать только курсор и останавливаться, когда hasMore равно false или nextCursor пуст: если проверять только hasMore, сбой на стороне сервера превращается в бесконечный цикл платных вызовов, который выглядит как зависание. iterTasks() делает всё это за вас.
Ошибки
Неудачи выбрасывают SpicyApiError с полями $businessCode, $requestId и $retryAfterSeconds. Ветвитесь по бизнес-коду и никогда по тексту сообщения: сообщения переводятся, коды — нет.
За HTTP 503 стоят три разных бизнес-кода, поэтому один статус не подскажет, что делать:
| Code | Значение | Что делать |
|---|---|---|
40003 | загруженные байты не совпадают с выданным разрешением | начните заново, с нового разрешения |
40004 | ни одна конфигурация не обслуживает такое сочетание параметров | измените параметр, названный в сообщении |
40201 | на балансе недостаточно средств | пополните баланс |
409 | ключ идемпотентности повторно использован для другого запроса | отправьте под этим ключом исходный запрос — или изменённый, но под новым ключом |
40901 | цена изменилась до того, как задача была создана | получите расчёт заново, сохранив тот же ключ идемпотентности |
503 | зависимость ненадолго недоступна | выдержите паузу по Retry-After |
50301 | сейчас нет ни рабочей конфигурации, ни цены | обновите каталог |
50302 | синхронная генерация завершилась ошибкой на стороне исполнителя, средства возвращены | повторите с новым ключом идемпотентности |
$error->guidance() возвращает ту же подсказку во время выполнения. Все коды перечислены на странице Ошибки и повторы.
Две вещи, которые сэкономят вам деньги
На одну отправку — один ключ идемпотентности, и используйте его при каждой повторной отправке: потерянный ответ не доказывает, что задача не создана. См. Идемпотентность.
Успешная задача оплачивается, даже если результат разочаровал. quote() ничего не резервирует и ничего не создаёт.
Чего этот пакет не делает
- Текстовые модели. Они говорят в форматах OpenAI, Anthropic и Gemini; проверенные клиенты на PHP для этих форматов уже работают с
https://api.spicyapi.ai/v1. См. Текст и потоковые ответы. - Код на стороне браузера. Ключу место на вашем сервере и нигде больше — уж точно не в том, что вы отдаёте посетителю.
- Выбор модели и заполнение её полей. Пакет отдаёт вам каталог и схему каждой модели; что именно отправлять, решаете вы.
- Отмена задачи. Принятая задача выполняется до конца.
Исходный код
Пакет распространяется под лицензией MIT и разрабатывается открыто по адресу github.com/spicyapi-ai/spicy-php. Его README — самая короткая полная экскурсия по клиенту, а в каталоге examples/ лежат готовые к запуску скрипты; эта страница добавляет к ним понятия вокруг.
Что дальше
Каталог моделей и схемы
Как читать inputSchema модели и выбирать допустимые входные данные
Асинхронные задачи
Состояния задач, опрос и значение каждого поля
Расчёт цены и совместимость
Точная цена запроса ещё до того, как вы за него заплатите
Вебхуки
Формат доставки, расписание повторов и проверка подписи
Ошибки
Все коды ошибок и какие из них стоит повторять
Java SDK
Тот же процесс на Java, с запечатанной иерархией ошибок
Go SDK
Официальный SDK SpicyAPI для Go (github.com/spicyapi-ai/spicy-go): установка, расчёт стоимости и первая генерация, загрузка локального файла, обход истории задач и разбор всех кодов ошибок.
Java SDK
Официальный SDK SpicyAPI для Java (ai.spicyapi spicyapi-java): установка, расчёт стоимости и первая генерация, загрузка локального файла, проверка обратных вызовов и исчерпывающий switch по запечатанной иерархии ошибок.

