Migrer depuis OpenAI ou OpenRouter
Adaptez du code OpenAI à SpicyAPI : URL de base, clé et ID de modèle, différences de paramètres et champs OpenRouter à retirer.
Si votre code appelle déjà un modèle de chat par les bibliothèques OpenAI officielles ou un outil proposant un réglage « compatible OpenAI », le passage à SpicyAPI demande trois modifications : l’URL de base, la clé API et l’ID du modèle. Cette page les détaille, puis explique les différences de comportement pour éviter un 400 inattendu à la migration. Pour des programmes d’exemple complets, voir Recettes de chat.
Les trois modifications
| Réglage | Valeur sur SpicyAPI |
|---|---|
| URL de base | https://api.spicyapi.ai/v1 |
| Clé API | Clé SpicyAPI commençant par sk-spicy-, envoyée comme Authorization: Bearer |
| Modèle | ID exact d’un modèle de texte du catalogue actuel, par exemple dans SPICY_MODEL ; voir IDs des modèles |
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)Les bibliothèques OpenAI lisent aussi OPENAI_BASE_URL et OPENAI_API_KEY dans l’environnement. Les définir permet d’exécuter le code sans modification, mais redirige aussi tous les autres appels OpenAI du même processus : préférez donc passer explicitement les valeurs comme ci-dessus.
IDs des modèles
Les IDs de modèles de texte ont la forme publisher/model/chat. Copiez-les depuis le catalogue actuel plutôt que d’adapter un nom connu d’un autre service :
GET /api/v1/models?modality=textliste uniquement les modèles de texte avec leurs tarifs et ajoute leurinputSchemaavecincludeSchema=1. Voir Catalogue des modèles et schémas.GET /v1/modelsest la liste au format OpenAI lue par les outils. Sans filtre, elle inclut des modèles d’image, de vidéo et d’audio non appelables sur/v1/chat/completions; ajoutez?modality=textpour le texte seul. Outre les quatre champs OpenAI, chaque entrée amodalityettask. Les modèles de chat ont aussicontext_length,max_output_tokensetsupported_parameters, les champs de requête acceptés. Les bibliothèques OpenAI ignorent ces champs supplémentaires.
Un ID inexistant renvoie 404 avec code: "model_not_found" et param: "model" ; le message indique la forme de l’ID et, s’il en existe un, le modèle de chat appelable le plus proche. Appeler un modèle d’image ou de vidéo sur un point de terminaison de chat renvoie 400, indiquant que ce n’est pas un modèle de chat. Les IDs d’autres services peuvent ressembler aux nôtres sans être interchangeables : les noms d’éditeurs diffèrent, les suffixes comme :free n’existent pas ici, et les modèles de texte finissent par /chat.
Champs de requête
Chaque modèle publie les champs acceptés dans son inputSchema, et leurs noms dans supported_parameters sur /v1/models.
- Les champs d’échantillonnage non listés par le modèle sont ignorés.
temperature,top_p,top_k,min_p,top_a,frequency_penalty,presence_penaltyetrepetition_penaltychangent seulement le caractère aléatoire ou répétitif de la réponse. Beaucoup de clients les envoient par défaut : un modèle qui ne les prend pas en charge génère simplement sans eux. Cela vaut aussi pourn: 1, unstopvide,stopsur un modèle sans séquences d’arrêt etreasoning_effortsur un modèle sans niveaux de raisonnement. La réponse indique les champs ignorés dansX-Spicy-Ignored-Params, par exemplefrequency_penalty, presence_penalty. - Les valeurs hors des possibilités du modèle sont ramenées à la valeur acceptée la plus proche. Une valeur d’échantillonnage hors plage est bornée, une liste
stoptrop longue garde ses premières entrées, et un niveaureasoning_effortabsent est remplacé par le plus proche, le plus bas en cas d’égalité. La réponse liste chaque changement enname=valuedansX-Spicy-Adjusted-Params, par exemplereasoning_effort=low, temperature=1. - Tout autre champ non listé est refusé avec
400,code: "unsupported_parameter", unparamdésignant le champ et un message le nommant, par exemplelogprobs: is not a supported field. Ces champs changent ce que vous obtenez : vous ne payez donc jamais une réponse produite sans une option que vous pensiez avoir envoyée.
| Champ | Sur SpicyAPI |
|---|---|
messages | Même forme. Rôles system, developer, user, assistant et tool. Le texte fonctionne partout ; les parties image_url uniquement sur les modèles dont le schéma les liste |
max_tokens, max_completion_tokens | Tous deux acceptés, même sens. Les tokens de raisonnement y sont inclus. Chaque modèle a sa valeur par défaut et son plafond |
stream, stream_options | Pris en charge, selon les règles d’utilisation d’OpenAI : un flux contient l’utilisation seulement si include_usage est true ; voir Réponses et flux |
tools, tool_choice | Pris en charge sur tous les modèles de texte. Certains n’acceptent que auto et none pour tool_choice : imposer un outil nommé y renvoie 400 |
parallel_tool_calls | Uniquement sur les modèles qui le listent |
reasoning_effort | Sur la plupart des modèles, mais les valeurs autorisées diffèrent : certains ajoutent none, minimal, xhigh ou max, d’autres n’ont pas medium. Une valeur absente est remplacée par la plus proche : lisez l’enum du modèle si le niveau exact compte |
temperature, top_p, top_k, frequency_penalty, presence_penalty | Utilisés sur les modèles qui les listent, bornés à leur plage et ignorés ailleurs, avec mention dans X-Spicy-Ignored-Params. Plusieurs modèles n’acceptent aucun champ d’échantillonnage |
seed | Uniquement sur les modèles qui le listent |
stop | Utilisé sur les modèles qui le listent, tronqué au nombre maximal accepté ; ignoré sans séquences d’arrêt ou lorsqu’il est vide |
response_format | json_object sur les modèles qui listent le champ ; indisponible ailleurs |
user, metadata, store | Acceptés et ignorés |
n | 1 est ignoré ; toute autre valeur est refusée avec 400 sur les modèles qui ne le listent pas |
logprobs, top_logprobs, logit_bias et tout autre champ non listé | Refusés avec 400 |
Pour voir les champs et valeurs autorisés avant tout envoi, exécutez la commande dans Recettes de chat : avant de commencer.
Réponses et flux
Une réponse sans streaming est un objet chat.completion normal : reply.choices[0].message.content et reply.usage fonctionnent comme avant. Les différences sont limitées :
reasoning_content. Les modèles qui raisonnent avant de répondre renvoient ce texte dansmessage.reasoning_content, oudelta.reasoning_contenten streaming, à côté decontent. Il est déjà compté dansusage.completion_tokens.- L’ID est celui de la requête.
ida la formechatcmpl-req_..., et l’en-tête de réponsex-request-idporte la même valeurreq_.... Indiquez-la au support. Un second en-tête,X-Spicy-Task-Id, désigne la tâche portant la facture ; voir Coût et utilisation. - Aucun
system_fingerprintnilogprobs.
Les flux utilisent les mêmes événements chat.completion.chunk, se terminent par data: [DONE] et suivent les règles d’OpenAI pour l’utilisation :
- Utilisation uniquement sur demande. Sans
stream_options.include_usage, aucun fragment ne porteusageet tous ont une listechoicesnon vide. Avec"include_usage": true, un fragment supplémentaire avecchoicesvide et l’usagefinal arrive juste avant[DONE], et tous les autres ontusage: null. Les exemples de streaming OpenAI fonctionnent sans changement ; vérifierchoicesavant de lirechoices[0]sécurise ce dernier fragment. - Un
finish_reasonpar choix. Chaque choix se termine par exactement un fragment portant sonfinish_reason, quel que soit le modèle. - Lignes de maintien de connexion. Pendant le raisonnement, le flux comporte des commentaires
: keep-alivepour garder la connexion ouverte. Les bibliothèques OpenAI les ignorent ; un parseur maison doit aussi le faire.
Pour les longues réponses ou un reasoning_effort élevé, utilisez le streaming. Une requête sans streaming dépassant environ 30 secondes reste ouverte avec des espaces avant le corps JSON et l’en-tête X-Spicy-Keepalive: 1, que les bibliothèques OpenAI savent analyser. Mais si elle échoue ensuite, l’objet d’erreur arrive dans le corps d’une réponse 200, sans que ces bibliothèques lèvent une erreur. Voir Timeouts et streaming.
Erreurs
Les erreurs utilisent la forme OpenAI {"error": {"message", "type", "param", "code"}} et les statuts HTTP habituels : les bibliothèques OpenAI lèvent leurs exceptions normales. param désigne le champ concerné sous forme de chemin à points, par exemple temperature ou messages.1.content.0. code distingue les erreurs partageant un statut : unsupported_parameter pour un champ ou une partie de message non accepté, invalid_request pour un champ manquant ou une valeur refusée, content_rejected pour un refus de politique de contenu, également 400, et model_not_found avec 404. Une différence avec OpenAI : 402 indique que le solde ou un plafond de dépenses ne couvre pas la requête. Le tableau complet et les actions figurent dans Recettes de chat : en cas de problème.
Les bibliothèques OpenAI réessaient automatiquement les réponses 429 et 5xx. C’est sûr ici : une requête échouée n’est pas facturée. Si vous envoyez un Idempotency-Key, fournissez-le par appel et lisez d’abord Clés d’idempotence sur les API de texte.
Coût et utilisation
Les réponses donnent des nombres de tokens dans usage, pas un tarif. Les tarifs par million de tokens d’entrée, d’entrée en cache et de sortie figurent sur chaque page de modèle et dans le catalogue. Pour connaître le coût réel, lisez l’en-tête X-Spicy-Task-Id (job_…), présent pour toute requête acceptée, avec ou sans streaming, puis retrouvez la tâche par GET /api/v1/jobs/recordInfo?taskId=<job_…> : cost est définitif lorsque settled est true. Les entrées de GET /api/v1/jobs portent aussi le requestId de l’appel de création, correspondant à l’en-tête X-Request-Id, et Console → Journaux affiche les mêmes tâches. La lecture des en-têtes avec les bibliothèques OpenAI est montrée dans Recettes de chat : trouver le coût d’un appel ; voir aussi Devis et facturation.
Avant l’exécution, un montant est réservé sur votre solde pour couvrir les messages et l’intégralité des max_tokens de sortie, ou la valeur par défaut du modèle si vous l’omettez. Vous ne payez que l’utilisation réelle, mais la réservation doit d’abord tenir dans le solde : un faible solde et un grand max_tokens renvoient 402. Fixez un max_tokens réaliste ou des plafonds de dépenses sur la clé ; voir Facturation.
Les points de terminaison absents
La génération de texte est proposée au format OpenAI Chat Completions, OpenAI Responses, Anthropic Messages et Google Gemini ; voir Texte et streaming. Ces API OpenAI n’existent pas et renvoient 404 : /v1/embeddings, l’ancien /v1/completions, /v1/images/*, /v1/audio/*, /v1/moderations, /v1/files, /v1/batches, /v1/assistants et le fine-tuning. Les modèles d’image, de vidéo et d’audio s’appellent par l’API de tâches, et les vidéos aussi par /v1/videos.
Depuis OpenRouter
OpenRouter utilise aussi le format OpenAI ; les trois modifications précédentes s’appliquent : remplacez l’URL de base https://openrouter.ai/api/v1 par https://api.spicyapi.ai/v1, puis la clé et les IDs de modèles. Retirez ensuite les champs propres à OpenRouter, qui renvoient ici 400 :
| Champ OpenRouter | Action |
|---|---|
models (liste de repli) et route | Retirer. Envoyez un seul ID ; SpicyAPI choisit comment l’exécuter |
provider | Retirer. Il n’y a pas de choix de prestataire par requête |
reasoning, objet comme {"effort": "high"} | Converti en reasoning_effort à la racine ; enabled: false devient none si le modèle le permet. Les autres clés internes sont ignorées. Envoyer directement reasoning_effort est plus clair |
usage ({"include": true}) | Retirer. Les réponses sans streaming incluent toujours usage ; pour les flux, envoyez stream_options: {"include_usage": true} |
transforms, plugins | Retirer |
Les en-têtes HTTP-Referer et X-Title sont sans conséquence et ignorés.
Liste de vérification
- URL de base
https://api.spicyapi.ai/v1et clé SpicyAPI. - ID de modèle copié du catalogue actuel, finissant par
/chat. - Champs vérifiés contre l’
inputSchemadu modèle ; champs non liés à l’échantillonnage et non pris en charge retirés ; en-têtesX-Spicy-Ignored-ParamsetX-Spicy-Adjusted-Paramsexaminés une fois pour voir les changements. - Si vous envoyez
stream_options.include_usage, les boucles ignorent le dernier fragment dont la listechoicesest vide. max_tokensréaliste ou valeur par défaut du modèle, avec plafonds de dépenses sur la clé.- Les requêtes longues ou à raisonnement élevé utilisent
stream: true.
Pour continuer
Recettes de chat · Texte et streaming · Catalogue des modèles et schémas · Authentification · Facturation
Recettes de chat
Programmes complets en Python, Node.js et curl pour les conversations, le roleplay, le streaming dans votre interface et les appels d’outils.
Tutoriel : transformer une image en vidéo
Partez d’une image enregistrée sur votre ordinateur et repartez avec un fichier vidéo téléchargé. Chaque étape montre la CLI, le SDK TypeScript et le HTTP simple.

