API compatible OpenAI : migrer en trois lignes
Ce que veut vraiment dire « compatible OpenAI », les trois lignes qui basculent ton app vers une API européenne, et la liste honnête de ce qui ne suit pas.
Trois lignes. Tu fais pointer la base_url du client vers le nouveau fournisseur, tu changes la clé API et tu remplaces le nom du modèle par un nom de niveau. Tout le reste d’un appel chat completions, le tableau de messages, le streaming, les outils, les schémas JSON, reste exactement tel quel, parce que compatible OpenAI signifie implémenter le même contrat HTTP, pas simplement lui ressembler.
Le plus intéressant dans une migration, ce n’est pas le code. C’est ce qui ne suit pas : les endpoints qui n’existent pas encore, le comportement du modèle qui change avec le même prompt, et le code d’erreur que tu n’as sans doute jamais géré.

Que veut vraiment dire « compatible OpenAI » ?
Il s’agit du contrat d’un seul endpoint : POST /v1/chat/completions, avec le même corps de requête et la même forme de réponse, y compris le format server-sent events pour le streaming. Ce contrat est devenu l’interface par défaut du secteur, comme l’API de S3 l’est devenue pour le stockage objet, et pour la même raison : tout était déjà écrit pour lui.
Les SDK officiels sont de fins clients au-dessus de ce contrat. Dans la bibliothèque Python d’OpenAI, la base URL est un argument du constructeur : la faire pointer ailleurs est une opération prévue, pas un bricolage. Ta logique de relance, ton gestionnaire de streaming et tes définitions de types continuent de fonctionner.
Ce que ça ne veut pas dire, c’est la parité fonctionnelle sur toute la plateforme. Les services compatibles implémentent bien l’endpoint de chat et divergent ensuite, et c’est pour ça que la version honnête de cet article est surtout une liste de divergences.
from openai import OpenAIclient = OpenAI(api_key=os.environ["UNION_API_KEY"],base_url="https://api.europeanailab.eu/v1",)response = client.chat.completions.create(model="eu-standard",messages=[{"role": "user", "content": prompt}],stream=True,)
3 lignes changent : la clé, la base URL, et un nom de niveau à la place d’un nom de modèle.
Tout le reste de l’appel suit le même contrat : messages, streaming, outils, schémas JSON. Ce qui change, ce n’est pas la forme de la requête mais le modèle qui écrit la réponse, et c’est pour ça que la check-list plus bas parle d’évaluation plutôt que de code.
Les trois lignes, en pratique
Voici tout le changement en Python. Les SDK Node, Go et .NET prennent les deux mêmes arguments sous d’autres noms.
from openai import OpenAI
client = OpenAI(
api_key=os.environ["UNION_API_KEY"],
base_url="https://api.europeanailab.eu/v1",
)
response = client.chat.completions.create(
model="eu-standard",
messages=[{"role": "user", "content": prompt}],
stream=True,
)
Si ton code lit déjà la base URL dans une variable d’environnement, ce qui vaut la peine avant toute migration, c’est un changement de configuration et non le déploiement d’un nouveau code. Ça te donne aussi le retour arrière le moins cher possible : tu la fais pointer à nouveau vers l’ancien fournisseur.
Ce qui suit, ce qui change, ce qui manque
Le tableau qu’il te faut avant de planifier le travail.
| Fonction | Statut | Que faire |
|---|---|---|
| Chat completions | Marche tel quel | Rien |
| Streaming (SSE) | Marche tel quel | Demande la consommation dans le flux pour garder une facturation exacte |
| Function calling et outils | Mêmes formes | Reteste quel outil le modèle choisit, pas s’il sait le faire |
| Sortie structurée (schéma JSON) | Mêmes formes | Continue de valider de ton côté, toujours |
| Prompts système | Marchent | Prévois de retoucher la formulation, pas la structure |
| Noms de modèles | Changent | Utilise un niveau : eu-lite, eu-standard, eu-long, eu-pro |
| Consommation et coût | Changent | Lis les en-têtes de réponse plutôt qu’un tableau de bord |
| Embeddings, images, voix | Manquent | Garde ton fournisseur actuel pour ça |
| API batch | Manque | Dans la feuille de route, pas encore livrée |
| API à état de type Assistants | Manque | Pas prévue ; garde l’état de ton côté |
Trois de ces lignes sont des absences. Nous les publions sous forme de liste sur la page d’accueil au lieu de te les laisser découvrir en pleine migration, parce que « compatible » est un mot qu’ont usé jusqu’à la corde des services qui implémentent la moitié du contrat et s’en contentent.
Pourquoi les noms de modèles deviennent des noms de niveaux
Tu achètes un niveau, pas un modèle. eu-standard est le cheval de trait, eu-long accepte un million de tokens de contexte, eu-pro est le spécialiste du raisonnement lourd, et chacun correspond à un modèle open-weight nommé, publié avec sa licence sur la page de transparence de l’IA.
La raison, c’est le renouvellement. Les modèles open-weight sont remplacés tous les quelques mois, et inscrire en dur le nom du modèle du jour dans ton application impose un redéploiement à chaque fois que le secteur bouge. Un niveau est une promesse de capacité et de prix qui survit au remplacement du modèle en dessous, et quand nous le remplaçons, nous le disons au lieu de changer en douce ce que fait ton code.
Les licences comptent aussi, et elles sont assez courtes pour être lues. L’essentiel de ce que nous faisons tourner est sous Apache 2.0 ou MIT, et c’est pour ça que ces mêmes poids peuvent légalement être servis depuis du matériel européen.

Ce qui change dans les réponses, pas dans l’API
C’est la partie qui surprend les équipes, alors prévois du temps pour elle. Changer de fournisseur, c’est changer de modèle, et les modèles diffèrent d’une manière qu’aucune couche de compatibilité ne peut masquer.
La mise en forme dérive. Un prompt réglé pour qu’un modèle produise un markdown propre peut produire un préambule sur un autre. En général, une ligne suffit à corriger, mais c’est une correction.
Le jugement sur les appels d’outils diffère. La forme d’un appel de fonction est identique ; l’envie du modèle de l’appeler ne l’est pas. Il existe des modèles qui appellent trop vite et d’autres qui répondent de mémoire.
Les refus diffèrent. Certains modèles open-weight esquivent des questions factuelles sur la politique ou l’histoire. Nous considérons ça comme un défaut : nous réacheminons une fois vers un modèle sans cet angle mort, et nous te le signalons dans l’en-tête X-EU-Routed quand c’est le cas. Tout est décrit sur la page de transparence de l’IA.
La réponse pratique, c’est un jeu de test. Garde vingt vraies requêtes avec les sorties attendues, lance-les sur les deux fournisseurs et lis les différences toi-même. Vingt, c’est assez pour repérer les régressions de mise en forme et de choix d’outils, et assez peu pour que tu le fasses vraiment.
Coût et facturation après la migration
Le prix au token repose partout sur la même idée, mais l’endroit où tu lis les chiffres change. Chaque réponse indique ce qu’a coûté la requête :
X-EU-Tokens-InetX-EU-Tokens-Out, les tokens comptés.X-EU-Cost-EUR, ce qui a été retiré de ton solde pour cette requête.X-EU-Balance-EUR, ce qu’il reste.X-EU-Model-Tier, le niveau qui l’a réellement traitée.
Tes propres logs peuvent donc répondre à la question du coût par fonction, par client, par endpoint, sans attendre qu’une facture mensuelle vienne te contredire. Les tarifs derrière ces chiffres sont sur la page des tarifs, et la façon de raisonner sur le coût des tokens en général, y compris pourquoi la sortie coûte plus cher que l’entrée, est détaillée dans ce que coûte une API d’IA au token.
Une habitude à garder : règle max_tokens sur chaque appel. C’est le levier de maîtrise des coûts le plus efficace de toute API au token, et il tient en une ligne.
Erreurs, limites et le 402 que tu n’as pas géré
Ta gestion d’erreurs couvre sans doute les 429 et les 500. Le crédit prépayé en ajoute une.
- 402, solde insuffisant. La requête est refusée avec un lien de recharge, rien n’est facturé après coup et ton solde ne passe jamais sous zéro. Traite-la comme une erreur d’authentification : alerte, et ne relance pas en boucle.
- 429, limite de débit ou plafond. Respecte
Retry-After. Les plafonds, c’est toi qui les fixes, par clé, en euros par jour et par mois. - 502 et 503, service en amont indisponible. Relance avec un délai croissant. Ce qui se passe pendant une panne, et la dépendance à un seul fournisseur qui l’explique, est détaillé sur la page d’état.
- Les requêtes en échec sont gratuites. Si aucune réponse exploitable n’est revenue, rien n’est retiré de ton solde, et une requête réacheminée vers un modèle plus faible est facturée au plus bas des deux tarifs.
Le plafond des nouveaux comptes surprend tout le monde une fois : 25 € par jour pendant les 48 premières heures, quel que soit ton solde. Il existe parce qu’une clé qui fuite le premier jour est l’erreur la plus coûteuse que puisse faire une nouvelle intégration.
La compatibilité te rend-elle vraiment portable ?
En partie, et il vaut la peine d’être précis sur la partie en question. Le contrat d’échange est portable : n’importe quel service qui parle chat completions peut prendre la place de l’autre derrière le même client. Ça suffit à supprimer la forme la plus grossière d’enfermement, celle où partir veut dire réécrire ta couche d’intégration.
Ce qui n’est pas portable, c’est tout ce que tu as construit sur un modèle précis. Des prompts ajustés pendant des mois, des exemples few-shot choisis pour les manies d’un modèle, des jeux d’évaluation notés selon le style de sortie d’un modèle, et tous les seuils fixés en regardant ce modèle se comporter. Rien de tout ça ne se transfère proprement, et rien de tout ça n’apparaît sur un schéma d’architecture.
Traite donc le modèle comme une dépendance avec un numéro de version, pas comme une constante. Garde les prompts dans des fichiers plutôt qu’éparpillés dans le code, garde le jeu de test dans le dépôt, et note pourquoi un seuil vaut ce qu’il vaut. Les équipes qui font ça changent de fournisseur en un après-midi. Celles qui ne le font pas passent un trimestre à découvrir ce que leurs prompts compensaient.
Check-list de migration
Une demi-journée pour un petit service, un sprint pour quelque chose qui a du vrai trafic.
- Mets la base URL dans la configuration si ce n’est pas déjà fait. Tout ce qui suit est plus simple une fois que c’est le cas.
- Choisis un niveau selon la charge de travail, pas selon son nom. La classification et l’extraction vont sur le niveau pas cher ; seul le travail de raisonnement lourd a besoin du niveau cher.
- Construis le jeu de test. Vingt vraies requêtes, les sorties attendues, versionnées dans le dépôt.
- Fais tourner les deux fournisseurs en parallèle sur une petite part du trafic et compare les résultats.
- Branche les en-têtes de coût sur ce que tu journalises déjà.
- Ajoute le chemin du 402, et un plafond par clé tant que tu y es.
- Garde l’ancien fournisseur configuré pendant quinze jours. Une migration que tu peux annuler en changeant une ligne de configuration, c’est une migration que tu peux faire un mardi.
Contre quel fournisseur mener cette évaluation est une question en soi, et les quatre options réalistes sont comparées dans notre comparatif des fournisseurs d’IA européens. Les dix premières requêtes sont gratuites et la recharge minimale est de 10 €, donc l’évaluation coûte moins cher que la réunion pour décider de la faire. Le contrat complet, avec du curl et du Python à copier, est dans la documentation de l’API, et l’endroit où ces requêtes sont traitées, étape par étape, est expliqué dans où vont tes prompts.
Questions fréquentes
Que veut dire compatible OpenAI ?
Cela veut dire que le service implémente le même contrat HTTP que l’endpoint chat completions d’OpenAI : même corps de requête, même forme de réponse, même format de streaming. Comme les SDK officiels ne sont qu’une fine couche au-dessus de ce contrat, les faire pointer vers une autre base URL suffit en général.
Comment passer de l’API OpenAI à un autre fournisseur ?
Change la clé API, fais pointer la base_url du client vers le nouveau fournisseur et remplace le nom du modèle. Dans les SDK Python et Node d’OpenAI, la base URL est un argument du constructeur : le changement tient donc en trois lignes, la clé, la base URL et le nom du modèle.
Le streaming et le function calling marchent-ils encore après la migration ?
Chez Union AI, oui. Le streaming utilise le même format server-sent events et peut renvoyer la consommation de tokens à la fin du flux, et le function calling comme la sortie structurée par schéma JSON gardent les mêmes formes de requête et de réponse. Ce qui change, c’est le jugement du modèle sur le moment d’appeler un outil, et ça vaut la peine de le tester.
Quelles fonctions d’OpenAI manquent aux API compatibles ?
Ça dépend du fournisseur, et les plus honnêtes publient la liste. Chez Union AI, la version 1 se limite au contrat chat completions : pas d’endpoints d’embeddings, d’images ou de voix, pas d’API batch et pas d’API à état de type Assistants. Ces manques sont nommés dans la feuille de route au lieu d’être sous-entendus comme existants : vérifie ces quatre mêmes trous chez n’importe quel fournisseur compatible.
Mes prompts donneront-ils les mêmes réponses sur un modèle open-weight ?
Non, et deux modèles ne le font jamais. Les consignes de mise en forme, le comportement d’appel d’outils et la manière de refuser changent tous : traite un changement de fournisseur comme un changement de modèle. Garde vingt vraies requêtes comme jeu de test et compare les sorties avant de basculer.
Comment comparer les coûts avant de changer ?
Prends une semaine de trafic réel, compte séparément les tokens en entrée et en sortie, et chiffre les deux côtés par million de tokens à leurs prix catalogue. La sortie pèse le plus sur la plupart des factures : un fournisseur qui paraît bon marché en entrée peut revenir plus cher sur une charge de travail bavarde.
Puis-je continuer à utiliser le SDK OpenAI ?
Oui. Les SDK officiels d’OpenAI fonctionnent sans modification avec tout service qui implémente le même contrat : c’est tout l’intérêt de la compatibilité. Tu gardes ta logique de relance, ta gestion du streaming et tes types, et tu changes seulement la destination des requêtes.