spicyapiDocumentación
Texto y chat
Contenido principal

Migrar desde OpenAI u OpenRouter

Conecta tu código de OpenAI a SpicyAPI. Cambios de URL base, clave e ID de modelo, campos con otro comportamiento y ajustes que debes quitar de OpenRouter.

Si tu código ya llama a un modelo de chat mediante las bibliotecas oficiales de OpenAI o una herramienta con la opción «OpenAI-compatible», migrar a SpicyAPI requiere tres cambios: la URL base, la clave de API y el ID del modelo. Esta página enumera esos cambios y los comportamientos diferentes para evitar que la migración termine con un 400 inesperado. Para programas completos de ejemplo, consulta Ejemplos prácticos de chat.

Los tres cambios

AjusteValor en SpicyAPI
URL basehttps://api.spicyapi.ai/v1
Clave de APIUna clave de SpicyAPI (comienza con sk-spicy-), enviada como Authorization: Bearer
ModeloUn ID exacto de modelo de texto del catálogo en vivo, por ejemplo en SPICY_MODEL (consulta ID de modelos)
import os
from openai import OpenAI

# Before: client = OpenAI()   (reads OPENAI_API_KEY, calls api.openai.com)
client = OpenAI(api_key=os.environ["SPICY_API_KEY"], base_url="https://api.spicyapi.ai/v1")

reply = client.chat.completions.create(
    model=os.environ["SPICY_MODEL"],
    messages=[{"role": "user", "content": "Say hello in one sentence."}],
)
print(reply.choices[0].message.content)

Las bibliotecas de OpenAI también leen OPENAI_BASE_URL y OPENAI_API_KEY del entorno. Definir ambas permite ejecutar el código existente sin cambios, pero también redirige cualquier otra llamada a OpenAI del mismo proceso. Por eso, es preferible pasar los valores explícitamente, como en los ejemplos anteriores.

ID de modelos

Los ID de modelos de texto tienen el formato publisher/model/chat. Cópialos del catálogo en vivo en lugar de adaptar un nombre que conozcas de otro servicio:

  • GET /api/v1/models?modality=text enumera solo modelos de texto y sus precios, y añade el inputSchema de cada uno con includeSchema=1. Consulta Catálogo de modelos y esquemas.
  • GET /v1/models es la lista en formato OpenAI que consultan las herramientas. Sin un filtro, incluye modelos de imagen, video y audio que no pueden llamarse desde /v1/chat/completions; añade ?modality=text para obtener solo modelos de texto. Además de los cuatro campos de OpenAI, cada entrada contiene modality y task, y los modelos de chat también tienen context_length, max_output_tokens y supported_parameters (los campos de solicitud que acepta el modelo). Las bibliotecas de OpenAI ignoran los campos adicionales.

Un ID de modelo que no exista devuelve 404 con code: "model_not_found" y param: "model"; el mensaje muestra el formato del ID y, cuando existe uno, el modelo de chat disponible más cercano. Llamar a un modelo de imagen o video desde un endpoint de chat devuelve 400 e indica que no es un modelo de chat. Los ID de otros servicios suelen parecer similares, pero no son intercambiables: los nombres de los creadores se escriben de otra manera, aquí no existen sufijos como :free y los modelos de texto terminan en /chat.

Campos de solicitud

Cada modelo publica los campos que acepta en su inputSchema y, por nombre, en supported_parameters de /v1/models.

  • Los campos de muestreo que el modelo no enumera se ignoran. temperature, top_p, top_k, min_p, top_a, frequency_penalty, presence_penalty y repetition_penalty solo modifican la aleatoriedad o repetición de una respuesta. Como muchos clientes los envían de forma predeterminada, un modelo que no los admite simplemente genera sin ellos. Lo mismo ocurre con n: 1, un stop vacío, stop en un modelo sin secuencias de detención y reasoning_effort en un modelo sin niveles de razonamiento. La respuesta indica lo ignorado en el encabezado X-Spicy-Ignored-Params, por ejemplo frequency_penalty, presence_penalty.
  • Los valores que el modelo no admite se ajustan al valor aceptado más cercano. Un valor de muestreo fuera del rango del modelo se limita al extremo correspondiente; una lista stop más larga de lo permitido conserva sus primeras entradas; un nivel de reasoning_effort no disponible se convierte en el nivel admitido más cercano, o en el inferior si hay empate. La respuesta enumera cada cambio como name=value en el encabezado X-Spicy-Adjusted-Params, por ejemplo reasoning_effort=low, temperature=1.
  • Cualquier otro campo no enumerado por el modelo se rechaza con 400, code: "unsupported_parameter", param con el nombre del campo y un mensaje que lo identifica, por ejemplo logprobs: is not a supported field. Estos campos cambian lo que recibes, por lo que nunca pagas por una respuesta generada sin una opción que creías haber enviado.
CampoComportamiento en SpicyAPI
messagesMisma forma. Roles system, developer, user, assistant y tool. El contenido de texto funciona en todos los modelos; las partes image_url, solo donde el esquema las enumera
max_tokens, max_completion_tokensAmbos se aceptan y significan lo mismo. Los tokens de razonamiento cuentan dentro del límite. Cada modelo tiene su propio valor predeterminado y máximo
stream, stream_optionsCompatibles, con las reglas de uso de OpenAI: un stream solo incluye uso si include_usage es true (consulta Respuestas y streams)
tools, tool_choiceCompatibles con todos los modelos de texto. Algunos solo aceptan auto y none en tool_choice; forzar una herramienta concreta devuelve 400 en esos modelos
parallel_tool_callsSolo en los modelos que lo enumeran
reasoning_effortEn la mayoría de los modelos, pero los valores permitidos varían entre ellos: algunos añaden none, minimal, xhigh o max, y otros no tienen medium. Un valor no disponible se convierte en el más cercano admitido; consulta el enum del modelo si necesitas un nivel exacto
temperature, top_p, top_k, frequency_penalty, presence_penaltySe utilizan en los modelos que los enumeran, se limitan a su rango y se ignoran en los demás (con sus nombres en X-Spicy-Ignored-Params). Varios modelos no admiten ningún campo de muestreo
seedSolo en los modelos que lo enumeran
stopSe utiliza donde el modelo lo enumera y se recorta a su cantidad máxima; se ignora si el modelo no tiene secuencias de detención o si está vacío
response_formatjson_object en los modelos que enumeran el campo; no está disponible en los demás
user, metadata, storeSe aceptan y se ignoran
n1 se ignora; cualquier otro valor se rechaza con 400 en los modelos que no lo enumeran
logprobs, top_logprobs, logit_bias y cualquier otro campo no enumerado por el modeloSe rechazan con 400

Para consultar los campos de un modelo y sus valores permitidos antes de enviar nada, ejecuta el comando de Ejemplos de chat: antes de empezar.

Respuestas y streams

Una respuesta sin streaming es un objeto chat.completion normal, de modo que reply.choices[0].message.content y reply.usage funcionan como antes. Las diferencias son pequeñas:

  • reasoning_content. Los modelos que razonan antes de responder devuelven ese texto en message.reasoning_content, o en delta.reasoning_content durante el streaming, junto a content. Ya está incluido en usage.completion_tokens.
  • El ID es el ID de la solicitud. id tiene el formato chatcmpl-req_..., y el encabezado de respuesta x-request-id contiene el mismo valor req_.... Inclúyelo al contactar con soporte. Otro encabezado, X-Spicy-Task-Id, identifica la tarea donde se registra el cargo (consulta Costo y uso).
  • Sin system_fingerprint ni logprobs.

Los streams utilizan los mismos eventos chat.completion.chunk, terminan con data: [DONE] y siguen las reglas de uso de OpenAI:

  • El uso solo se incluye si se solicita. Sin stream_options.include_usage, ningún fragmento contiene usage y todos tienen una lista choices no vacía. Con "include_usage": true, llega un fragmento adicional con una lista choices vacía y el usage final justo antes de [DONE]; todos los demás tienen usage: null. El código basado en los ejemplos de streaming de OpenAI funciona sin cambios; comprobar choices antes de acceder a choices[0] evita errores en ese último fragmento.
  • Un finish_reason por alternativa. Cada alternativa termina con exactamente un fragmento que contiene su finish_reason, independientemente del modelo utilizado.
  • Líneas de mantenimiento de conexión. Mientras un modelo sigue razonando, el stream envía comentarios : keep-alive para mantener abierta la conexión. Las bibliotecas de OpenAI los omiten; un analizador propio también debe hacerlo.

Para respuestas largas o un reasoning_effort alto, utiliza streaming. Una solicitud sin streaming que supera unos 30 segundos se mantiene abierta con espacios antes del cuerpo JSON y el encabezado X-Spicy-Keepalive: 1, por lo que las bibliotecas de OpenAI siguen interpretándola. Sin embargo, si falla después de ese punto, el objeto de error llega en el cuerpo de una respuesta 200, que las bibliotecas no tratan como excepción. Consulta Tiempos de espera y streaming.

Errores

Los errores utilizan el formato OpenAI, {"error": {"message", "type", "param", "code"}}, con los códigos HTTP habituales, de modo que las bibliotecas de OpenAI lanzan sus excepciones normales. param identifica el campo relacionado con el error mediante una ruta con puntos, como temperature o messages.1.content.0, y code distingue errores que comparten estado: unsupported_parameter (un campo o parte de mensaje no admitido), invalid_request (un campo ausente o un valor no admitido), content_rejected (rechazo por política de contenido, también 400) y model_not_found (404). Una diferencia con OpenAI: se utiliza 402 cuando el saldo o un límite de gasto no cubre la solicitud. La tabla completa y las acciones para cada código están en Ejemplos de chat: cuando algo falla.

Las bibliotecas de OpenAI reintentan automáticamente las respuestas 429 y 5xx. Aquí es seguro: una solicitud fallida no se factura. Si envías un Idempotency-Key, pásalo en cada llamada y lee primero Claves de idempotencia en endpoints de texto.

Costo y uso

Las respuestas indican cantidades de tokens en usage, no un precio. Los precios de entrada, entrada en caché y salida se expresan por millón de tokens y aparecen en la página de cada modelo y en el catálogo. Para conocer el costo real de una llamada, lee el encabezado de respuesta X-Spicy-Task-Id (job_…), presente en todas las solicitudes aceptadas, con streaming o sin él, y consulta la tarea mediante GET /api/v1/jobs/recordInfo?taskId=<job_…>: cost es definitivo cuando settled es true. Las entradas de GET /api/v1/jobs también incluyen el requestId de la llamada que las creó (el encabezado X-Request-Id), y Consola → Logs muestra las mismas tareas. Ejemplos de chat: conocer el costo de una llamada explica cómo leer el encabezado desde las bibliotecas de OpenAI; consulta también Cotizaciones y facturación.

Antes de ejecutar una solicitud, se retiene del saldo un importe que cubre tus mensajes y todo el max_tokens de salida, o el valor predeterminado del modelo si lo omites. Solo pagas el uso real, pero primero debe haber saldo suficiente para la retención; por eso, un saldo pequeño con un max_tokens grande devuelve 402. Define un max_tokens realista o configura límites de gasto en la clave; consulta Facturación.

Endpoints que no encontrarás aquí

La generación de texto está disponible como OpenAI Chat Completions, OpenAI Responses, Anthropic Messages y Google Gemini; los detalles aparecen en Texto y streaming. Estos endpoints de OpenAI no existen y devuelven 404: /v1/embeddings, el antiguo /v1/completions, /v1/images/*, /v1/audio/*, /v1/moderations, /v1/files, /v1/batches, /v1/assistants y fine-tuning. Los modelos de imagen, video y audio se llaman mediante la API de tareas; los videos también pueden llamarse mediante /v1/videos.

Si vienes de OpenRouter

OpenRouter también utiliza el formato OpenAI, así que se aplican los tres cambios anteriores: sustituye la URL base https://openrouter.ai/api/v1 por https://api.spicyapi.ai/v1 y cambia la clave y los ID de modelos. Después elimina los campos exclusivos de OpenRouter; aquí devuelven 400:

Campo de OpenRouterQué hacer
models (una lista de alternativas) y routeElimínalos. Envía un ID de modelo; SpicyAPI decide cómo ejecutarlo
providerElimínalo. No hay selección del servicio de ejecución por solicitud
reasoning (un objeto como {"effort": "high"})Se convierte en el campo raíz reasoning_effort (enabled: false pasa a none si el modelo lo admite); las demás claves internas se ignoran. Enviar reasoning_effort directamente resulta más claro
usage ({"include": true})Elimínalo. Las respuestas sin streaming siempre incluyen usage; para streams, envía stream_options: {"include_usage": true}
transforms, pluginsElimínalos

Los encabezados HTTP-Referer y X-Title no causan problemas y se ignoran.

Lista de comprobación

  1. URL base https://api.spicyapi.ai/v1 y una clave de SpicyAPI.
  2. ID del modelo copiado del catálogo en vivo y terminado en /chat.
  3. Campos de solicitud comprobados con el inputSchema del modelo; campos ajenos al muestreo no admitidos eliminados; encabezados de respuesta X-Spicy-Ignored-Params y X-Spicy-Adjusted-Params revisados una vez para saber qué cambió.
  4. Si envías stream_options.include_usage, los bucles de streaming omiten el fragmento final cuya lista choices está vacía.
  5. max_tokens con un valor realista o con el predeterminado del modelo, y límites de gasto en la clave.
  6. Las solicitudes largas o con mucho razonamiento utilizan stream: true.

Sigue leyendo

Ejemplos prácticos de chat · Texto y streaming · Catálogo de modelos y esquemas · Autenticación · Facturación

En esta página