OpenAPI est un format qui décrit une API HTTP dans un simple fichier YAML ou JSON. On y trouve la liste des chemins exposés par l'API, les méthodes que chacun accepte, les champs transmis dans la requête et ce que renvoie la réponse. Le fichier se lit aussi bien par un humain que par un programme, ce qui permet d'en tirer la documentation, les bibliothèques clientes et les serveurs simulés à partir d'une source unique.
Son nom officiel est OpenAPI Specification, abrégé en OAS. Elle est maintenue par l'OpenAPI Initiative, un projet à gouvernance ouverte hébergé par la Linux Foundation.
La suite : comment le document est structuré, ce qui distingue OpenAPI de Swagger, ce qui a changé en 3.1 et 3.2, et pourquoi la promesse centrale du format — une documentation qui ne se périme jamais — ne se réalise pas toute seule.
À quoi ressemble un document OpenAPI
Un fichier fonctionnel est plus court qu'on ne l'imagine :
openapi: 3.1.0
info:
title: Liens
version: "1.0"
paths:
/links:
post:
summary: Créer un lien raccourci
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [url]
properties:
url:
type: string
format: uri
responses:
'201':
description: Lien créé
Deux champs seulement sont obligatoires au niveau racine, openapi et info ; le document doit en outre contenir au moins l'un de paths, components ou webhooks. C'est ce que pose la spécification OAS 3.1.1.
Un piège se cache dès ces premières lignes : openapi et info.version sont deux choses distinctes. Le premier désigne la version du format lui-même, celle que les outils lisent pour savoir comment interpréter le document. Le second désigne la version de votre API. La spécification le dit explicitement, mais l'intuition suggère l'inverse, d'où ces fichiers qui déclarent openapi: 1.0.
Les spécifications volumineuses sont généralement réparties sur plusieurs fichiers via $ref : le document racine pointe vers ./paths/links.yaml, qui pointe vers ./schemas/link.yaml. Pratique, et source d'un problème sur lequel je reviens à la fin.
OpenAPI et Swagger ne sont pas la même chose
C'est la confusion la plus répandue sur le sujet.
| Ce que c'est | Qui le maintient | |
|---|---|---|
| OpenAPI | la norme, le format lui-même | OpenAPI Initiative, Linux Foundation |
| Swagger | une famille d'outils qui lisent ce format | SmartBear |
L'histoire explique l'embrouille. À l'origine, Swagger désignait à la fois la spécification et les outils. La spécification a ensuite été confiée à l'OpenAPI Initiative et rebaptisée OpenAPI Specification, tandis que le nom Swagger est resté aux outils : Swagger UI, Swagger Editor et les autres.
Concrètement, « on utilise Swagger » signifie presque toujours « on a un fichier OpenAPI et on l'affiche avec Swagger UI ». Rien ne lie les deux : le même fichier s'ouvre tout aussi bien dans Redoc, dans Scalar ou dans un générateur de clients.
Versions : 3.0, 3.1 et 3.2
Les écarts entre branches ne sont pas cosmétiques.
3.0 reste la plus déployée. Sa faiblesse tient aux schémas de données : ils ressemblent à JSON Schema sans lui être entièrement compatibles, ce qui casse régulièrement l'interopérabilité entre outils.
3.1 a comblé cet écart. L'objet Schema est devenu un sur-ensemble de JSON Schema Draft 2020-12, si bien que les schémas déjà maintenus en JSON Schema se réutilisent au lieu d'être réécrits. La révision en vigueur, 3.1.1, a été publiée le 24 octobre 2024.
3.2 est sortie en septembre 2025. D'après l'annonce de l'OpenAPI Initiative, les ajouts les plus notables sont :
- la méthode HTTP
query, pour les lectures idempotentes dont les paramètres ne tiennent pas dans l'URL ; additionalOperations, pour les méthodes non standard ;- les types de médias en flux : Server-Sent Events, JSON Lines, séquences JSON ;
- des étiquettes hiérarchiques, via
summary,parentetkind; - le flux d'appareil OAuth 2.0, pour les équipements à saisie malcommode.
Mieux vaut choisir la version que vos outils prennent réellement en charge plutôt que le numéro le plus élevé. La prise en charge de 3.1 dans l'écosystème est solide ; 3.2 est plus récente et une partie de la chaîne d'outils la rattrape encore. Notre propre spécification déclare 3.1.0.
Ce que la spécification apporte en pratique
Un seul fichier couvre plusieurs besoins à la fois :
- documentation interactive — Swagger UI ou Redoc construisent la page directement depuis le fichier, formulaire d'essai compris ;
- bibliothèques clientes — les générateurs produisent un SDK dans votre langage, au lieu d'enveloppes écrites à la main ;
- serveurs simulés — un mock démarre à partir de la description avant que le backend existe, et le front n'attend pas ;
- tests de contrat — les tests confrontent les réponses réelles aux schémas et repèrent l'instant où une réponse cesse de correspondre à sa description.
C'est de cette liste que vient l'argument principal en faveur d'OpenAPI : la documentation cesse d'être un texte séparé que quelqu'un oublie de mettre à jour.
L'argument est juste. Il n'est simplement pas automatique.
Rien ne relie la spécification au code
C'est la partie que les articles d'introduction n'abordent presque jamais, et celle qui mord en production.
Le fichier de spécification n'est qu'un fichier. Le routeur ignore son existence. Le contrôleur aussi. Vous pouvez supprimer un point de terminaison, en ajouter un autre, rendre facultatif un champ obligatoire : pas un seul test ne tombera. La documentation ne se synchronise pas d'elle-même ; elle reste exacte exactement aussi longtemps que quelqu'un pense à la modifier.
L'écart se creuse dans les deux sens, et chacun nuit à sa façon :
- documenté mais non fonctionnel. Le développeur lit la documentation, envoie la requête et reçoit un 404. La confiance dans le reste de la documentation s'effondre aussitôt : si c'était faux ici, où encore ?
- fonctionnel mais non documenté. Pour qui la cherche, la fonctionnalité n'existe tout simplement pas. Personne ne la trouve, personne ne la paie.
Le second cas est le plus dangereux, parce que personne ne se plaint. Il n'y a d'ailleurs personne pour le faire : l'utilisateur ignore que ce point de terminaison existe.
C'est précisément ce qui nous est arrivé. L'API de Lix.li comptait trois points de terminaison de conversions en état de marche — une liste d'événements ainsi que la réception de postbacks, à l'unité et par lots — et aucun ne figurait dans la spécification. Le code tournait, les tests passaient, et pendant ce temps la page de présentation de l'API affichait deux chemins tout autres, absents du code. Cela est apparu lors d'une vérification complète, pas sur un signalement.
Détecter l'écart automatiquement
Cela ne se règle pas par la discipline mais par un test. L'idée est simple : prendre la liste des chemins de la spécification, prendre la liste des routes du routeur, et comparer les deux ensembles dans les deux sens.
chemins d'openapi.yaml → la route existe-t-elle ? → sinon la documentation ment
routes du routeur → le chemin est-il décrit ? → sinon la fonction est invisible
La moitié « ce qui est documenté fonctionne » se vérifie le plus commodément par une requête réelle : appeler le point de terminaison sans identifiants et s'assurer que la réponse est 401 et non 404. La distinction compte. 401 signifie que la route a été trouvée et réclame une clé ; 404, qu'aucun chemin de ce nom n'existe. Rien n'est écrit au passage, puisque l'authentification s'exécute avant le contrôleur.
La vérification inverse lit la configuration des routes. Elle oblige en prime à énumérer explicitement les exceptions : les points de terminaison internes qui n'ont rien à faire dans un contrat public. Cette liste vaut à elle seule le détour, car elle force à décider une fois, délibérément, ce qui constitue votre API publique.
Nous avons écrit ce test après l'épisode des conversions. Il s'est justifié immédiatement : retirez à nouveau les conversions de la spécification et le test échoue en nommant les deux chemins manquants.
Deux pièges qu'on découvre tard
$ref et chemins relatifs. Lorsqu'une spécification est répartie en plusieurs fichiers, les références internes se résolvent par rapport à l'URL depuis laquelle le fichier a été servi. Un document racine accessible à /openapi ira chercher ./paths/links.yaml à l'adresse /paths/links.yaml et ne trouvera rien. Dans le navigateur tout paraît correct, parce que Swagger UI charge le fichier depuis son emplacement réel — mais un générateur de clients pointé sur le même document échouera. Soit vous ne servez la spécification que depuis son propre répertoire, soit vous la compilez en un fichier unique sans $ref externes.
Swagger UI est vide pour un robot d'indexation. Une page Swagger UI se résume à quelques lignes de balisage et à un script qui dessine le contenu dans le navigateur. Le HTML source ne contient pas un seul chemin de point de terminaison. Pour le lecteur la page est correcte ; pour l'indexation elle est blanche. Ajoutez-y une erreur fréquente dans le robots.txt : une règle Disallow: /api/, écrite pour masquer les points de terminaison JSON, masque du même coup la documentation destinée aux humains logée sous le même préfixe. Il faut bloquer le chemin versionné exact — /api/1.0/, par exemple — et non la section entière.
Par où commencer
Si l'API existe déjà et que la description manque, l'ordre est à peu près celui-ci :
- Décrivez un point de terminaison à la main. Cela suffit à saisir la structure.
- Choisissez la version que vos outils maîtrisent sans hésiter ; 3.1 par défaut.
- Découpez le fichier avec
$refdès qu'il déborde d'un écran, et fixez immédiatement l'URL depuis laquelle il est servi. - Mettez en place un test qui compare la spécification au routeur dans les deux sens. Avant cette étape, toute affirmation sur une documentation à jour reste une promesse, pas une propriété.
Pour voir ce que cela donne sur une API réelle, la documentation de l'API Lix.li couvre les liens, les groupes, les tests A/B et les conversions. Le service bâti autour de cette API est présenté sur la page de l'API de raccourcissement d'URL. Sur un sujet voisin, l'analyse des redirections 301, 302, 307 et 308 explique notamment pourquoi une 301 transforme un POST en GET — ce qui concerne directement les points de terminaison de postback évoqués plus haut. Et si vous préférez recevoir les événements plutôt que les interroger, il faudra passer par les webhooks.