spicyapiDocumentação
SDKs
Conteúdo principal

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 PHPEste SDK
Chamá-la a partir de Node.js ou TypeScriptO SDK para TypeScript
Chamá-la a partir de Python, Go ou JavaO SDK para Python, Go ou Java
Gerar conteúdo pelo terminal, sem escrever códigoA 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 textoUm cliente PHP consolidado para os formatos da OpenAI, da Anthropic ou do Gemini — veja Texto e streaming

Instalar

composer require spicyapi/spicyapi

Coloque 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_decode seguido de json_encode muda 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 com 0e… as compara como números — "0e123" == "0e456" é true. Esta classe usa hash_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ódigoO que significaO que fazer
40003os bytes enviados não correspondem ao ticket delesrecomece com um ticket novo
40004nenhuma implantação atende a essa combinação de parâmetrosmude o parâmetro citado na mensagem
40201saldo insuficienteadicione saldo
409a chave de idempotência foi reaproveitada para outra requisiçãoreenvie a requisição original com aquela chave, ou a requisição alterada com uma chave nova
40901o preço mudou antes de a tarefa ser criadacote de novo, mantendo a mesma chave de idempotência
503uma dependência está indisponível por pouco tempoespere o que o Retry-After pedir
50301nenhuma implantação nem preço utilizáveis neste momentoatualize o catálogo
50302uma geração síncrona falhou no processamento e foi estornadatente 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

Nesta página