Contrato de Spicy Schema
Formato compartido de solicitudes, campos canónicos, esquemas de formularios, ciclo asíncrono, errores, almacenamiento y compatibilidad.
Spicy Schema es el contrato público de todos los modelos de medios nuevos. Añadir un modelo modifica su model, inputSchema y precio para el cliente; la autenticación, las tareas, los resultados, los errores y las descargas siguen siendo iguales.
Esta es una página de referencia. La mayoría de las integraciones solo necesitan Catálogo de modelos y esquemas. Continúa si estás construyendo un formulario, un validador o un generador de código a partir de inputSchema y quieres saber exactamente qué puedes dar por establecido.
Solo el catálogo en vivo confirma que un modelo puede ejecutarse
Esta página define la forma del contrato; no afirma que ningún modelo esté disponible. Antes de ejecutar trabajos de pago, consulta la respuesta autenticada de GET /api/v1/models y exige tanto enabled: true como available: true. Las entradas previstas o deshabilitadas no pueden ejecutarse.
Alcance público
Un ID público de modelo identifica una capacidad estable con el formato <publisher>/<model>/<task>. El creador es el autor o la organización de investigación responsable del modelo. La edición de imágenes siempre utiliza el sufijo edit, y los identificadores publicados anteriormente siguen aceptándose. Copia siempre un ID real del catálogo en vivo; MODEL_ID_FROM_CATALOG en esta página es un marcador de posición.
Las respuestas públicas, los webhooks, los errores y las descargas solo utilizan identificadores, códigos de error y URL propios de SpicyAPI. Nunca incluyen detalles del procesamiento interno, respuestas o errores sin procesar ni datos comerciales internos. Los resultados se copian al almacenamiento de SpicyAPI antes de devolverse.
Formato unificado de creación
{
"model": "MODEL_ID_FROM_CATALOG",
"input": {
"prompt": "slow camera push-in",
"image_url": "spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3",
"duration_seconds": 8,
"resolution": "1080p",
"generate_audio": true
},
"callBackUrl": "https://your-app.example.com/hooks/spicy"
}| Campo | Regla |
|---|---|
model | Valor exacto del catálogo autenticado en vivo que pueda ejecutarse actualmente |
input | Debe cumplir el inputSchema actual de esa misma entrada; los campos desconocidos se rechazan |
callBackUrl | Dirección pública HTTPS opcional que recibe la tarea terminada; respeta exactamente estas mayúsculas. Consulta Webhooks |
El input anterior es ilustrativo: solo el inputSchema de cada modelo define cuáles de estos campos acepta.
Campos canónicos
Un mismo concepto utiliza un solo nombre snake_case en todos los endpoints nuevos. Esta tabla muestra campos habituales, no todos; el inputSchema del modelo siempre contiene la lista completa.
| Concepto | Campo | Tipo |
|---|---|---|
| Prompt positivo | prompt | Cadena |
| Prompt negativo | negative_prompt | Cadena |
| Imagen principal / final | image_url / last_image_url | Referencia de imagen (consulta la explicación siguiente) |
| Varias imágenes de entrada | image_urls | Array de referencias de imagen |
| Máscara de edición | mask_url | Referencia de imagen |
| Video / audio principal | video_url / audio_url | URL HTTPS pública o URI spicy:// confirmado |
| Medios de referencia | reference_image_urls / reference_video_urls / reference_audio_urls | Array de referencias |
| Duración de salida | duration_seconds | Número o entero |
| Resolución / proporción | resolution / aspect_ratio | Enumeración documentada |
| Tamaño exacto de salida | size (un valor predefinido), o width / height | Enumeración documentada / entero |
| Tasa de fotogramas | fps | Entero |
| Audio nativo | generate_audio | Booleano |
| Semilla aleatoria | seed | Entero |
| Pesos de adaptadores | loras, o high_noise_loras / low_noise_loras cuando el endpoint expone sus etapas por separado | Array de objetos |
| Cantidad de salidas | num_outputs | Entero positivo |
| Codificación | output_format | Enumeración documentada |
| Conversación de chat | messages | Array |
| Longitud de salida / muestreo | max_tokens / temperature | Entero / número |
Una referencia de imagen es una URL HTTPS pública, un URI spicy:// confirmado de una subida o un URI pequeño data:image/…;base64,…. El video y el audio no pueden enviarse en línea dentro de la solicitud. Consulta Medios.
Una entrada de pesos de adaptador identifica un conjunto de pesos y la intensidad con la que se aplica. El nombre del campo es fijo, pero la forma de cada entrada no lo es: el endpoint determina si acepta una URL de archivo de pesos o una clave de una biblioteca integrada mediante su subesquema items. Léelo allí en lugar de suponer un formato. Lo mismo se aplica a la cantidad de entradas admitidas y a los subcampos obligatorios.
Un campo específico del modelo solo existe cuando representa una capacidad real que ningún campo canónico cubre, y recibe el nombre de esa capacidad. Un endpoint antiguo puede conservar nombres de campo anteriores en su inputSchema en vivo; los endpoints nuevos no los copian.
inputSchema y anotaciones de interfaz
Todos los modelos utilizan JSON Schema 2020-12 en inputSchema. El mismo esquema controla la validación del servidor, los formularios de Playground, los ejemplos y las cotizaciones. El SDK oficial de TypeScript tipa estáticamente los formatos compartidos de solicitudes y respuestas; el input de cada modelo sigue siendo Record<string, unknown> y debe validarse con el esquema en vivo, en lugar de dar por hecho que existen campos generados para cada modelo.
Este sitio no mantiene tablas de parámetros por modelo. Los parámetros tienen una sola fuente: el inputSchema devuelto por GET /api/v1/models?includeSchema=1, que también utiliza Playground. Una copia estática se desactualiza cuando cambian los modelos y no hay forma de detectar siempre ese desfase. Para construir tu propia tabla, consulta Catálogo de modelos y esquemas.
Cada esquema que recibes:
- tiene una raíz
objecty rechaza campos desconocidos (additionalProperties: false); - especifica
required, tipos, enumeraciones, rangos, longitudes, límites de arrays, formatos URI, valores predeterminados y condiciones verificadas; - proporciona a cada propiedad una
descriptionen inglés que no está vacía; - omite los campos y valores predeterminados no verificados en lugar de suponerlos;
- expresa condiciones mediante
anyOf/allOfen la raíz, como se explica a continuación, nunca solo mediante la visibilidad en la interfaz; - no contiene interruptores que solo cambien el procesamiento interno ni controles de filtros de contenido.
Anotaciones de presentación: x-ui
Los metadatos de presentación no constituyen un segundo esquema de validación. Todos se encuentran en el objeto x-ui de cada propiedad, cuyas claves usan snake_case y pertenecen a un conjunto cerrado:
| Clave | Significado |
|---|---|
widget | Control que debe mostrarse; los valores se enumeran más abajo |
order | Orden de los campos, de mayor a menor, único dentro de un endpoint |
label / placeholder | Etiqueta alternativa a la predeterminada; texto de ejemplo de la entrada |
rows | Número de filas del área de texto |
step / unit | Paso numérico y sufijo de unidad (s, px) |
accept / max_size_mb | Filtro MIME y tamaño máximo por archivo en los controles de subida |
advanced | Pertenece a una sección avanzada plegada |
primary | Entrada principal; se fija arriba y nunca se pliega |
affects_price | Cambiar este valor exige una cotización nueva |
visible_when | Expresión de visibilidad condicional |
enum_labels | Correspondencia entre valor de enumeración y etiqueta visible |
x-ui.widget toma uno de trece valores: textarea, text, upload, multi-upload, select, radio, slider, number, switch, json, object-list, chat-messages, hidden. El control siempre es coherente con la forma de los datos, de modo que puedes elegir directamente el renderizador correspondiente: select y radio siempre tienen un enum; slider siempre tiene tanto minimum como maximum (nunca solo exclusiveMinimum / exclusiveMaximum); switch siempre es boolean; multi-upload es un array con items.type: string; object-list es un array con items.type: object e items.properties, y cada subcampo tiene su propio type y description; chat-messages es un array; hidden indica que el campo se envía aunque no tenga un control visible.
En cualquier campo de array, sus propios minItems / maxItems y el subesquema items contienen restricciones que no aparecen en la lista raíz required, y el servidor las aplica igual que el resto del esquema. En un object-list, items es un esquema de objeto completo: tiene su propio required, su propio additionalProperties: false y el tipo, rango y default de cada subcampo. Los valores predeterminados también se aplican dentro de esas entradas: si omites un subcampo, se completa con su default antes de validar. Así, un subcampo obligatorio que tiene un valor predeterminado es opcional en la práctica. Si no tiene valor predeterminado, se rechaza cuando falta; también se rechazan los subcampos desconocidos y los valores fuera de rango.
Un 400 causado por un valor dentro de un array señala la entrada exacta —campo, índice desde cero y subcampo— en lugar de toda la solicitud. Comunica esa ubicación a quien deba corregirla: con varias entradas, saber cuál está fuera de rango permite una corrección directa en lugar de una búsqueda por descarte.
Los clientes deben ignorar las claves de presentación desconocidas: añadir una clave x-ui es un cambio compatible con versiones anteriores.
{
"type": "object",
"required": ["prompt", "duration_seconds"],
"additionalProperties": false,
"properties": {
"prompt": {
"type": "string",
"minLength": 1,
"description": "Describes the scene and its motion.",
"x-ui": { "widget": "textarea", "order": 100, "primary": true, "rows": 4 }
},
"duration_seconds": {
"type": "integer",
"enum": [5, 8, 10],
"description": "Output duration in seconds.",
"x-ui": { "widget": "radio", "order": 90, "unit": "s", "affects_price": true }
}
},
"x-pricing": { "variantFields": ["duration_seconds"], "billingMode": "variant_matrix" }
}Todas las description son texto en inglés y pueden mostrarse sin cambios como ayuda del campo.
x-ui.affects_price: true indica a la interfaz que debe renovar una cotización cuando cambia el campo. No es una fórmula de precios ni sustituye a pricing o quantityField del catálogo. El x-pricing raíz expresa esa misma información para el servidor: variantFields enumera los campos que seleccionan un nivel de precio y siempre es un subconjunto de los campos affects_price: true, mientras que billingMode indica cómo se mide el uso del endpoint. Ambos son orientativos; el importe autorizado es el devuelto por el catálogo, la cotización y la tarea. La forma de los niveles en pricing se explica en Cómo leer los precios.
Campos condicionales
Las condiciones adoptan exactamente dos formas, ambas en la raíz:
{
"anyOf": [{ "required": ["image_url"] }, { "required": ["image_urls"] }],
"allOf": [
{ "if": { "required": ["last_image_url"] }, "then": { "required": ["image_url"] } },
{ "if": { "properties": { "resolution": { "const": "4k" } } }, "then": { "required": ["seed"] } }
]
}Cada rama de anyOf contiene solo required y significa «proporciona al menos uno de estos campos». Cada rama de allOf contiene un if → then: if se activa con required o properties.<field>.const, then declara únicamente required, y significa «si proporcionas A, debes proporcionar B».
oneOf, not, dependentSchemas y dependentRequired nunca aparecen en el inputSchema de un modelo: pueden expresar la restricción, pero no representarse como formulario, por lo que el usuario solo la descubriría después de enviar mediante un 400 difícil de explicar. Implementar las dos formas anteriores cubre todos los endpoints condicionales.
Ciclo de vida asíncrono
queued -> running -> succeeded
-> failed
-> expiredcreateTask devuelve un ID de tarea de Spicy. Consulta recordInfo o recibe un webhook firmado de Spicy. queued y running no son estados finales; los demás sí. Los registros históricos canceled siguen siendo legibles por compatibilidad, pero las tareas nuevas no ofrecen una acción de cancelación. Tareas asíncronas es la referencia de los estados.
Un estado no final desconocido dentro de un modelo se convierte en running hasta recibir un estado final conocido o alcanzar el plazo absoluto de la plataforma. Una solicitud sin idempotencia cuya confirmación sea incierta no se reenvía a ciegas, para evitar generaciones y cargos duplicados.
Respuestas y errores unificados
Todas las API JSON nativas (/api/v1) utilizan el mismo formato; los endpoints compatibles /v1 y /v1beta utilizan los de sus propios protocolos:
{
"code": 200,
"msg": "success",
"data": {},
"request_id": "req_01k3m8x9q2z4v7n5p6r8s0t1w2"
}data no aparece en un fallo síncrono. Usa code para la lógica, nunca el msg que puede cambiar; proporciona request_id a soporte. Un fallo posterior a la creación de la tarea utiliza data.state: "failed" y un errorCode estable. errorMessage está pensado para las personas: si el servicio del modelo comunicó un motivo concreto, se muestra ese motivo en inglés sin nombres internos, direcciones ni datos de cuenta; en otros casos, se muestra un mensaje de la plataforma en tu idioma de errores. Usa errorCode para la lógica, nunca errorMessage.
Resultados y descargas
Una tarea correcta enumera sus archivos en output.assets[]. Cada archivo listo ya contiene un enlace de descarga:
{
"assets": [
{
"key": "tasks/2026/08/31/job_01k3m8x9q2z4v7n5p6r8s0t1w3/asset_01.mp4",
"url": "https://example.r2.cloudflarestorage.com/results/asset_01.mp4?X-Amz-Signature=SIGNATURE_FROM_RESPONSE",
"expiresAt": "2026-08-31T10:20:00Z",
"mime": "video/mp4",
"durationSeconds": 8,
"bytes": 1234567
}
]
}Descarga url directamente, sin tu clave de API. Normalmente dura 20 minutos; vuelve a consultar la tarea para obtener una nueva mientras el resultado siga conservado, como máximo durante 14 días. Mientras un archivo se almacena, aparece con pending: true y sin url; si el almacenamiento falló definitivamente, tiene unavailable: true. key identifica el archivo para el endpoint opcional download-url, pero no lo necesitas para descargar. Todos los enlaces apuntan al almacenamiento propio de SpicyAPI.
Facturación al cliente
pricing, startingPrice y quantityField del catálogo y estimatedCost / cost de la tarea describen únicamente los precios para el cliente. Los importes son cadenas decimales en USD. Los datos comerciales internos quedan fuera del contrato público y nunca aparecen en catálogos, tareas, webhooks ni registros exportados.
Compatibilidad y retirada de funciones
Cambios que debes tolerar sin romper la integración: un campo opcional nuevo, un campo nuevo de respuesta que puedes ignorar o una anotación x-ui desconocida.
Un campo obligatorio nuevo, la eliminación de un valor de enumeración, un cambio de significado o tipo, un límite más estricto o un ID público de modelo modificado requieren una versión nueva del modelo o un período de retirada anunciado. Los ID publicados permanecen inmutables; las migraciones utilizan un alias de compatibilidad o una redirección explícita.
Guarda el catálogo autenticado en caché como se describe en Caché del catálogo. Nunca mantengas una copia permanente del esquema ni recurras a una página estática cuando el catálogo en vivo no tenga una entrada que pueda ejecutarse.
Límites de solicitudes
Cómo funcionan los límites de lectura y escritura, qué consume cada uno, qué otros límites puedes alcanzar y por qué la concurrencia es independiente.
Tareas aceptadas y reintentos
Por qué una tarea de generación aceptada no puede cancelarse y cómo reintentar de forma segura las tareas fallidas o vencidas.

