SDK para PHP
O SDK oficial da SpicyAPI para PHP (spicyapi/spicyapi) — instale com o Composer, cote e faça a sua primeira geração, envie um arquivo local, verifique webhooks com segurança e trate todos os códigos de erro.
O spicyapi/spicyapi é o SDK oficial para PHP da SpicyAPI. Ele coloca a geração de imagem, vídeo e áudio atrás de um único SpicyClient: anexa a sua chave de API, desembrulha o envelope da resposta em arrays associativos comuns, espera por uma tarefa com pausas crescentes e limitadas, envia arquivos locais numa única chamada e verifica assinaturas de webhook sem nenhum dos três erros que o código PHP costuma cometer nessa hora.
Ele exige PHP 8.1 ou mais recente, além de ext-json e ext-hash, e não tem nenhuma dependência de biblioteca. A ext-curl é usada quando está disponível; sem ela, o cliente recorre ao stream wrapper https://.
Esta é a ferramenta certa?
| Você quer… | Use |
|---|---|
| Chamar a SpicyAPI a partir do seu próprio código PHP | Este SDK |
| Chamá-la a partir de Node.js ou TypeScript | O SDK para TypeScript |
| Chamá-la a partir de Python, Go ou Java | O SDK para Python, Go ou Java |
| Gerar conteúdo pelo terminal, sem escrever código | A CLI |
| Deixar um assistente de IA chamar a SpicyAPI por você | O servidor MCP e o Agent Skill |
| Usar um modelo de conversa ou de texto | Um cliente PHP consolidado para os formatos da OpenAI, da Anthropic ou do Gemini — veja Texto e streaming |
Instalar
composer require spicyapi/spicyapiColoque a sua chave no ambiente, para que ela nunca apareça no seu código nem no controle de versão:
export SPICY_API_KEY="sk-spicy-..."Seu primeiro resultado
Peça o preço, decida, crie a tarefa, espere, leia os arquivos. Cotar é grátis e não reserva nada.
use SpicyApi\SpicyClient;
$client = new SpicyClient(); // lê SPICY_API_KEY do ambiente
$quote = $client->quote('MODEL_ID_FROM_CATALOG', ['prompt' => 'a lantern in fog']);
printf("at most %s\n", $quote['maxCharge']); // decida antes de gastar
$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";
}Toda chamada devolve o data já decodificado do envelope da resposta, como um array associativo comum: leia com $quote['maxCharge'], não com $quote->maxCharge.
MODEL_ID_FROM_CATALOG é um marcador. Pegue um ID de verdade no catálogo autenticado — $client->listModels(), que é GET /api/v1/models — e nunca de memória nem de uma página antiga: modelos são acrescentados, pausados e aposentados. Monte a entrada a partir do inputSchema daquele modelo, que listModels() devolve; cada modelo aceita campos diferentes.
Os valores são texto decimal em dólares americanos, nunca floats, porque números binários de ponto flutuante não guardam a maioria dos valores decimais com exatidão. Compare-os com SpicyClient::compareUsd(). Não existem créditos nem pontos em lugar nenhum desta API.
O waitForTerminal retorna em qualquer estado terminal, e um estado terminal não é necessariamente um estado de sucesso. Confira o estado antes de ler a saída e, nos endpoints que respondem com texto em vez de arquivos, leia primeiro SpicyClient::outputText($final).
Pule a primeira consulta
O waitSeconds mantém a conexão aberta até a tarefa terminar, então um trabalho curto dispensa qualquer laço de consulta. Ele não promete uma tarefa concluída. Se o orçamento de espera acabar, você recebe a resposta comum de tarefa aceita — mesmo formato, estado diferente —, então decida pelo estado, nunca pelo fato de ter passado o parâmetro:
$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']);
}Começar por um arquivo local
Imagem para vídeo, troca de personagem e edição de imagem precisam do seu material do nosso lado antes de mais nada. O uploadFile faz o envio de três etapas — ticket, PUT para o armazenamento e commit — numa única chamada e devolve uma URI spicy://. É essa URI que vai na entrada.
$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(),
);Uma URL HTTPS acessível publicamente pode ir direto na entrada e não precisa de envio nenhum. Imagens são aceitas até 10 MiB; áudio e vídeo, até 90 MiB. Veja Mídia.
Webhooks
use SpicyApi\SpicyWebhook;
// O segredo vem primeiro. Os outros argumentos são opcionais: $_SERVER e os bytes
// crus de php://input são lidos por você, a menos que um framework já os tenha consumido.
$delivery = SpicyWebhook::verifyRequest(getenv('SPICY_WEBHOOK_SECRET') ?: '');
// Dentro de um framework que guarda o corpo em buffer, passe você mesmo os bytes exatos:
$delivery = SpicyWebhook::verifyRequest(
secret: getenv('SPICY_WEBHOOK_SECRET') ?: '',
server: $_SERVER,
rawBody: $request->getContent(),
);Três jeitos de o código PHP errar isso, todos evitados por esta classe:
- Reserializar o corpo antes de verificar. Um
json_decodeseguido dejson_encodemuda os bytes, e a assinatura nunca mais vai bater. Verifique os bytes crus. - Comparar assinaturas com
==ou===. Além da questão de tempo, o==entre duas strings que ambas se pareçam com0e…as compara como números —"0e123" == "0e456"étrue. Esta classe usahash_equals. - Pular a conferência do horário. Uma assinatura válida continua válida para sempre; sem uma janela de validade, uma entrega capturada pode ser reenviada por terceiros.
As entregas são repetidas, então elimine duplicatas pelo ID da entrega. O formato completo das entregas e o calendário de novas tentativas estão em Webhooks.
Saldo, uso e histórico
$balance = $client->getBalance(); // disponível / reservado / total, em texto USD
$usage = $client->getUsage('2026-09-01', '2026-09-08'); // gasto liquidado em [from, to)
foreach ($client->iterTasks(state: 'succeeded') as $summary) {
echo $summary['taskId'], "\n"; // todas as páginas, com os filtros repetidos por você
}O held é dinheiro reservado por tarefas que ainda não liquidaram, então ele não está gasto nem disponível. As datas são dias do calendário em UTC, no formato YYYY-MM-DD, formando um intervalo semiaberto [from, to) de no máximo 92 dias. As três chamadas enxergam apenas a chave de API que as fez: chaves irmãs e gerações feitas no console não entram.
O getUsage() serve para conferência contábil, não para acompanhar o andamento. Ele carrega um orçamento de requisições que vale para a conta inteira e é compartilhado por todas as chaves, e esse limitador falha fechando: ficar consultando pode trancar a conta inteira para fora dos próprios relatórios. Acompanhe uma tarefa com getTask().
O listTasks() devolve uma página como {items, hasMore, nextCursor}. Paginar à mão significa repetir cada filtro sem alterar nada e mover apenas o cursor, parando quando hasMore for falso ou nextCursor estiver vazio — conferir só o hasMore transforma uma falha do lado do servidor num laço infinito de chamadas pagas que parece um travamento. O iterTasks() cuida de tudo isso.
Erros
As falhas lançam SpicyApiError, com $businessCode, $requestId e $retryAfterSeconds. Decida pelo código de negócio, nunca pela mensagem — mensagens são traduzidas, códigos não.
O HTTP 503 é compartilhado por três códigos de negócio diferentes, então o status sozinho não diz o que fazer:
| Código | O que significa | O que fazer |
|---|---|---|
40003 | os bytes enviados não correspondem ao ticket deles | recomece com um ticket novo |
40004 | nenhuma implantação atende a essa combinação de parâmetros | mude o parâmetro citado na mensagem |
40201 | saldo insuficiente | adicione saldo |
409 | a chave de idempotência foi reaproveitada para outra requisição | reenvie a requisição original com aquela chave, ou a requisição alterada com uma chave nova |
40901 | o preço mudou antes de a tarefa ser criada | cote de novo, mantendo a mesma chave de idempotência |
503 | uma dependência está indisponível por pouco tempo | espere o que o Retry-After pedir |
50301 | nenhuma implantação nem preço utilizáveis neste momento | atualize o catálogo |
50302 | uma geração síncrona falhou no processamento e foi estornada | tente de novo com uma chave de idempotência nova |
$error->guidance() devolve o mesmo conselho em tempo de execução. Todos os códigos estão listados em Erros.
Duas coisas que vão economizar o seu dinheiro
Use uma chave de idempotência por envio e reaproveite-a em cada reenvio — uma resposta perdida não prova que a tarefa não foi criada. Veja Idempotência.
Uma tarefa que dá certo é cobrada, mesmo que o resultado decepcione. O quote() não reserva nada e não cria nada.
O que este pacote não faz
- Modelos de texto. Eles falam os formatos de rede da OpenAI, da Anthropic e do Gemini; os clientes PHP consolidados para esses formatos já funcionam contra
https://api.spicyapi.ai/v1. Veja Texto e streaming. - Código que roda no navegador. A chave é do seu servidor, nunca de algo que você entrega a um visitante.
- Escolher o modelo ou preencher os campos dele. O pacote entrega o catálogo e o schema de cada modelo; quem decide o que enviar é você.
- Cancelar uma tarefa. Depois de aceita, ela vai até o fim.
Código-fonte
O pacote tem licença MIT e é desenvolvido abertamente em github.com/spicyapi-ai/spicy-php. O README dele é o passeio completo mais curto pelo cliente, e a pasta examples/ traz scripts prontos para rodar; esta página acrescenta os conceitos em volta.
Próximos passos
Catálogo de modelos e schemas
Como ler o inputSchema de um modelo e escolher entradas válidas
Tarefas assíncronas
Estados da tarefa, polling e o que significa cada campo
Cotações e compatibilidade
Veja o preço exato de uma requisição antes de pagá-lo
Webhooks
Formato das entregas, calendário de novas tentativas e verificação de assinatura
Erros
Todos os códigos de erro e quais valem uma nova tentativa
SDK para Java
O mesmo fluxo em Java, com uma taxonomia de erros selada
SDK para Go
O SDK oficial da SpicyAPI para Go (github.com/spicyapi-ai/spicy-go) — instale, cote e faça a sua primeira geração, envie um arquivo local, percorra o seu histórico de tarefas e trate todos os códigos de erro.
SDK para Java
O SDK oficial da SpicyAPI para Java (ai.spicyapi spicyapi-java) — instale, cote e faça a sua primeira geração, envie um arquivo local, verifique callbacks e use um switch exaustivo sobre uma taxonomia de erros selada.

