SDK de PHP
El SDK oficial de PHP para SpicyAPI (spicyapi/spicyapi) — instálalo con Composer, cotiza y lanza tu primera generación, sube un archivo local, verifica webhooks sin riesgos y trata todos los códigos de error.
spicyapi/spicyapi es el SDK oficial de PHP para SpicyAPI. Pone la generación de imagen, video y audio detrás de un único SpicyClient: adjunta tu clave de API, desenvuelve la envoltura de la respuesta en arrays asociativos normales, espera a una tarea con esperas crecientes acotadas, sube archivos locales en una sola llamada y verifica las firmas de los webhooks sin cometer ninguno de los tres errores en los que el código PHP suele caer ahí.
Necesita PHP 8.1 o posterior, más ext-json y ext-hash, y no tiene ninguna dependencia de bibliotecas. Usa ext-curl cuando está presente; sin ella, el cliente recurre al envoltorio de flujo https://.
¿Es la herramienta adecuada?
| Quieres… | Usa |
|---|---|
| Llamar a SpicyAPI desde tu propio código de PHP | Este SDK |
| Llamarla desde Node.js o TypeScript | El SDK de TypeScript |
| Llamarla desde Python, Go o Java | El SDK de Python, de Go o de Java |
| Generar cosas desde una terminal, sin escribir código | La CLI |
| Que un asistente de IA llame a SpicyAPI por ti | El servidor MCP y el Agent Skill |
| Usar un modelo de chat o de texto | Un cliente de PHP consolidado para los formatos de OpenAI, Anthropic o Gemini; consulta Texto y streaming |
Instálalo
composer require spicyapi/spicyapiGuarda tu clave en el entorno, para que no aparezca nunca ni en tu código ni en el control de versiones:
export SPICY_API_KEY="sk-spicy-..."Tu primer resultado
Pide el precio, decide, crea la tarea, espera y lee los archivos. Cotizar es gratis y no retiene nada.
use SpicyApi\SpicyClient;
$client = new SpicyClient(); // lee SPICY_API_KEY del entorno
$quote = $client->quote('MODEL_ID_FROM_CATALOG', ['prompt' => 'a lantern in fog']);
printf("at most %s\n", $quote['maxCharge']); // decide 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";
}Cada llamada devuelve el data ya decodificado de la envoltura de la respuesta como un array asociativo normal, así que léelo con $quote['maxCharge'], no con $quote->maxCharge.
MODEL_ID_FROM_CATALOG es un marcador de posición. Saca un ID real del catálogo autenticado —$client->listModels(), que es GET /api/v1/models— y nunca de memoria ni de una página antigua: los modelos se añaden, se pausan y se retiran. Construye la entrada a partir del inputSchema del propio modelo, que devuelve listModels(); cada modelo acepta campos distintos.
Los importes son cadenas decimales en dólares estadounidenses, nunca números en coma flotante, porque un flotante binario no puede guardar con exactitud la mayoría de los importes decimales. Compáralos con SpicyClient::compareUsd(). En esta API no hay créditos ni puntos por ninguna parte.
waitForTerminal devuelve el control en cualquier estado terminal, y un estado terminal no es un estado correcto. Comprueba el estado antes de leer la salida, y lee primero SpicyClient::outputText($final) en los endpoints que responden con texto en lugar de con archivos.
Sáltate la primera consulta
waitSeconds mantiene la conexión abierta hasta que la tarea termina, así que un trabajo corto no necesita ningún bucle de sondeo. No promete que la tarea esté terminada. Si se agota ese margen, recibes la respuesta normal de aceptación —misma forma, distinto state—, así que ramifica según el estado, nunca según el hecho de que hayas pasado el 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']);
}Empieza desde un archivo local
La generación de imagen a video, la sustitución de personas y la edición de imágenes necesitan que tu material esté antes de nuestro lado. uploadFile hace en una sola llamada la subida en tres pasos —ticket, PUT al almacenamiento y confirmación— y devuelve un URI spicy://. Ese URI es lo que va en la 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(),
);Una URL HTTPS accesible públicamente puede ir directamente en la entrada y no necesita ninguna subida. Se aceptan imágenes de hasta 10 MiB, y audio y video de hasta 90 MiB; consulta Carga y descarga de medios.
Webhooks
use SpicyApi\SpicyWebhook;
// El secreto va primero. Los demás argumentos son opcionales: se leen por ti $_SERVER
// y los bytes en bruto de php://input, salvo que un framework ya los haya consumido.
$delivery = SpicyWebhook::verifyRequest(getenv('SPICY_WEBHOOK_SECRET') ?: '');
// Dentro de un framework que almacene el cuerpo en un búfer, pásale tú los bytes exactos:
$delivery = SpicyWebhook::verifyRequest(
secret: getenv('SPICY_WEBHOOK_SECRET') ?: '',
server: $_SERVER,
rawBody: $request->getContent(),
);Tres formas en las que el código PHP se equivoca aquí, y que esta clase evita todas:
- Volver a serializar el cuerpo antes de verificarlo. Un
json_decodeseguido de unjson_encodecambia los bytes, y la firma ya no volverá a coincidir. Verifica los bytes en bruto. - Comparar firmas con
==o===. Más allá del tiempo que tardan,==sobre dos cadenas que empiezan por0e…las compara como números:"0e123" == "0e456"datrue. Aquí se usahash_equals. - Saltarse la comprobación de la marca de tiempo. Una firma válida sigue siendo válida para siempre; sin una ventana de frescura, una entrega capturada se puede reproducir más tarde.
Las entregas se reintentan, así que deduplica por el ID de entrega. El formato completo de las entregas y el calendario de reintentos están en Webhooks.
Saldo, consumo e historial
$balance = $client->getBalance(); // disponible / retenido / total, en cadenas USD
$usage = $client->getUsage('2026-09-01', '2026-09-08'); // gasto liquidado en [from, to)
foreach ($client->iterTasks(state: 'succeeded') as $summary) {
echo $summary['taskId'], "\n"; // todas las páginas, repitiendo los filtros por ti
}held es el dinero que retienen las tareas que aún no se han liquidado, así que no está ni gastado ni disponible. Las fechas son días naturales en UTC, YYYY-MM-DD, y forman un intervalo semiabierto [from, to) de 92 días como máximo. Las tres llamadas solo ven la clave de API que las hace: ni las claves hermanas ni las generaciones de la consola se incluyen.
getUsage() sirve para cuadrar cuentas, no para seguir el progreso. Lleva un presupuesto de solicitudes común a toda la cuenta que comparten todas las claves, y ese limitador falla en cerrado: sondearlo puede dejar a la cuenta entera sin acceso a sus propios informes. Para seguir una tarea usa getTask().
listTasks() devuelve una página como {items, hasMore, nextCursor}. Paginar a mano significa repetir todos los filtros sin cambiarlos y mover solo el cursor, y parar cuando hasMore sea falso o nextCursor esté vacío: comprobar solo hasMore convierte un fallo puntual del servidor en un bucle infinito de llamadas de pago que parece un cuelgue. iterTasks() hace todo eso por ti.
Errores
Los fallos lanzan SpicyApiError, que lleva $businessCode, $requestId y $retryAfterSeconds. Ramifica según el código de negocio, nunca según el mensaje: los mensajes se traducen, los códigos no.
El estado HTTP 503 lo comparten tres códigos de negocio distintos, así que el estado por sí solo no te dice qué hacer:
| Code | Qué significa | Qué hacer |
|---|---|---|
40003 | los bytes subidos no coinciden con su ticket | empieza de cero con un ticket nuevo |
40004 | ningún despliegue atiende esa combinación de parámetros | cambia el parámetro que nombra el mensaje |
40201 | saldo insuficiente | añade fondos |
409 | la clave de idempotencia se reutilizó para una solicitud distinta | reenvía la solicitud original con esa clave, o la solicitud cambiada con una clave nueva |
40901 | el precio cambió antes de que se creara la tarea | vuelve a cotizar y conserva la misma clave de idempotencia |
503 | una dependencia no está disponible durante un momento | espera lo que indique Retry-After |
50301 | ahora mismo no hay ningún despliegue ni precio utilizable | actualiza el catálogo |
50302 | una generación síncrona falló aguas arriba y se reembolsó | reintenta con una clave de idempotencia nueva |
$error->guidance() devuelve ese mismo consejo en tiempo de ejecución. Todos los códigos están en Errores.
Dos cosas que te ahorrarán dinero
Usa una sola clave de idempotencia por envío y reutilízala en cada reenvío: que se pierda una respuesta no demuestra que la tarea no se haya creado. Consulta Idempotencia.
Una tarea que sale bien se cobra, aunque el resultado te decepcione. quote() no retiene nada y no crea nada.
Lo que este paquete no hace
- Modelos de texto. Hablan los formatos de OpenAI, Anthropic y Gemini; los clientes de PHP consolidados para esos formatos ya funcionan contra
https://api.spicyapi.ai/v1. Consulta Texto y streaming. - Código del lado del navegador. La clave vive en tu servidor, nunca en nada que envíes a quien visita tu sitio.
- Elegir un modelo o rellenar sus campos. Te entrega el catálogo y el esquema de cada modelo; tú decides qué enviar.
- Cancelar una tarea. Cuando una tarea se acepta, se ejecuta hasta el final.
Código fuente
El paquete tiene licencia MIT y se desarrolla en abierto en github.com/spicyapi-ai/spicy-php. Su README es el recorrido completo más corto por el cliente, y examples/ contiene scripts listos para ejecutar; esta página añade los conceptos que lo rodean.
Siguientes pasos
Catálogo de modelos y esquemas
Cómo leer el inputSchema de un modelo y elegir entradas válidas
Tareas asíncronas
Estados de las tareas, sondeo y qué significa cada campo
Cotizaciones y compatibilidad
Consulta el precio exacto de una solicitud antes de pagarlo
Webhooks
Formato de las entregas, calendario de reintentos y verificación de la firma
Errores
Todos los códigos de error y cuáles vale la pena reintentar
SDK de Java
El mismo flujo desde Java, con una taxonomía de errores sellada
SDK de Go
El SDK oficial de Go para SpicyAPI (github.com/spicyapi-ai/spicy-go) — instálalo, cotiza y lanza tu primera generación, sube un archivo local, recorre tu historial de tareas y trata todos los códigos de error.
SDK de Java
El SDK oficial de Java para SpicyAPI (ai.spicyapi spicyapi-java) — instálalo, cotiza y lanza tu primera generación, sube un archivo local, verifica las llamadas de retorno y recorre de forma exhaustiva una taxonomía de errores sellada.

