Chapitre 726 minIntermédiaire
Documenter une API avec OpenAPI et Swagger
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- distinguer OpenAPI et Swagger ;
- lire et compléter un document OpenAPI 3.1 ;
- choisir entre la description d'abord et le code d'abord ;
- rédiger le README d'une API.
4 exercices corrigés
OpenAPI est le format standard pour décrire une API REST dans un fichier YAML ou JSON : ses adresses, ses méthodes, ses paramètres, la forme de ses requêtes et de ses réponses, ses codes d'erreur et son authentification. Swagger désigne aujourd'hui les outils qui exploitent ce fichier, dont Swagger UI, qui en tire une documentation interactive. Ce chapitre présente la structure d'un document OpenAPI 3.1, un exemple complet pour AppliFrais, les outils et les deux façons de travailler (la description d'abord, ou le code d'abord).
OpenAPI ou Swagger ?
Les deux noms désignent des choses liées mais différentes, et la confusion est fréquente.
- Swagger est né en 2011 comme format de description d'API, accompagné d'outils. Fin 2015, son éditeur, SmartBear, a confié le format à un consortium, l'OpenAPI Initiative (sous l'égide de la Linux Foundation), qui l'a renommé OpenAPI Specification (OAS) en 2016. La version 3.0, en 2017, est la première publiée sous ce nom.
- Swagger est resté le nom de la famille d'outils de l'éditeur SmartBear : Swagger UI (la documentation interactive), Swagger Editor (l'éditeur en ligne), Swagger Codegen (la génération de code).
Dire « la doc Swagger de mon API » reste courant et compris ; en toute rigueur, tu écris un document OpenAPI, que Swagger UI affiche.
| Version | Année | Ce qu'il faut savoir |
|---|---|---|
| Swagger 2.0 | 2014 | Encore croisée dans de vieux projets ; mot-clé swagger: "2.0" |
| OpenAPI 3.0 | 2017 | Refonte : components, requestBody, plusieurs serveurs |
| OpenAPI 3.1 | 2021 | Les schémas deviennent du JSON Schema 2020-12 complet ; c'est la version la plus prise en charge par les outils aujourd'hui |
| OpenAPI 3.2 | Septembre 2025 | Compatible avec la 3.1 ; ajoute notamment des étiquettes hiérarchiques, les réponses en flux et d'autres méthodes HTTP comme QUERY. Les outils l'adoptent progressivement |
Pour un nouveau projet, écris de l'OpenAPI 3.1 : c'est ce que ce chapitre utilise.
Pourquoi documenter avec OpenAPI
Une documentation écrite à la main dans un traitement de texte est fausse au bout de trois semaines. Un document OpenAPI est lisible par les machines, ce qui change tout :
- une documentation interactive générée automatiquement : chaque route, ses paramètres, des exemples, et un bouton pour l'essayer ;
- la validation automatique des requêtes et des réponses, à partir des schémas ;
- la génération de code : un client TypeScript, PHP ou C# prêt à l'emploi, ou le squelette du serveur ;
- un serveur simulé (mock) qui répond selon les exemples, pour que le front avance avant que l'API existe ;
- l'import dans les clients de test (Bruno, Postman, Insomnia), qui créent une requête par route ;
- un inventaire de toutes les routes exposées, utile pour la sécurité : ce qui n'est pas documenté ne devrait pas exister.
La structure d'un document OpenAPI
Un document OpenAPI est un objet YAML (ou JSON) dont les clés de premier niveau sont toujours les mêmes :
| Clé | Contenu |
|---|---|
openapi | La version de la spécification : 3.1.2 |
info | Le titre, la version de ton API (à ne pas confondre avec la précédente), une description |
servers | Les adresses de base : production, préproduction, poste local |
security | L'authentification exigée par défaut pour toutes les opérations |
tags | Des groupes pour ranger les opérations dans la documentation |
paths | Les routes : pour chaque chemin, les opérations (get, post…) avec leurs paramètres, leur corps et leurs réponses |
components | Les éléments réutilisables : schémas de données, réponses, paramètres, schémas de sécurité, référencés avec $ref |
Un exemple complet : l'API d'AppliFrais
Voici un extrait réel du document OpenAPI d'AppliFrais, avec trois opérations. Lis-le en entier : chaque bloc est commenté juste après.
openapi: 3.1.2
info:
title: API AppliFrais
version: 1.4.0
description: Gestion des fiches de frais des visiteurs médicaux du laboratoire GSB.
servers:
- url: https://api.gsb.example/v1
description: Production
- url: http://localhost:3000/v1
description: Poste du développeur
security:
- jeton: []
tags:
- name: Fiches
description: Les fiches de frais mensuelles
paths:
/fiches:
get:
tags: [Fiches]
summary: Lister les fiches
description: Un visiteur ne reçoit que ses fiches ; un comptable reçoit toutes les fiches.
operationId: listerFiches
parameters:
- name: etat
in: query
schema:
$ref: '#/components/schemas/Etat'
- name: mois
in: query
schema:
type: string
pattern: '^\d{4}-(0[1-9]|1[0-2])$'
example: '2026-10'
- name: page
in: query
schema: { type: integer, minimum: 1, default: 1 }
- name: limite
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
responses:
'200':
description: Une page de fiches
content:
application/json:
schema:
type: object
required: [donnees, pagination]
properties:
donnees:
type: array
items:
$ref: '#/components/schemas/Fiche'
pagination:
$ref: '#/components/schemas/Pagination'
'401':
$ref: '#/components/responses/NonAuthentifie'
post:
tags: [Fiches]
summary: Créer la fiche d'un mois
operationId: creerFiche
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NouvelleFiche'
responses:
'201':
description: Fiche créée
headers:
Location:
description: URI de la nouvelle fiche
schema: { type: string }
content:
application/json:
schema:
$ref: '#/components/schemas/Fiche'
'401':
$ref: '#/components/responses/NonAuthentifie'
'409':
$ref: '#/components/responses/Conflit'
'422':
$ref: '#/components/responses/Invalide'
/fiches/{id}:
parameters:
- name: id
in: path
required: true
schema: { type: integer }
get:
tags: [Fiches]
summary: Lire une fiche
operationId: lireFiche
responses:
'200':
description: La fiche
content:
application/json:
schema:
$ref: '#/components/schemas/Fiche'
'401':
$ref: '#/components/responses/NonAuthentifie'
'404':
$ref: '#/components/responses/Introuvable'
components:
securitySchemes:
jeton:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
Etat:
type: string
enum: [CR, CL, VA, MP]
description: CR en cours de saisie, CL clôturée, VA validée, MP mise en paiement
Fiche:
type: object
required: [id, visiteurId, mois, etat, montantValide]
properties:
id: { type: integer, readOnly: true }
visiteurId: { type: integer, readOnly: true }
mois: { type: string, example: '2026-10' }
etat:
$ref: '#/components/schemas/Etat'
montantValide:
type: [number, 'null']
description: Fixé par le comptable à la validation
NouvelleFiche:
type: object
required: [mois]
additionalProperties: false
properties:
mois: { type: string, pattern: '^\d{4}-(0[1-9]|1[0-2])$' }
Pagination:
type: object
properties:
page: { type: integer }
limite: { type: integer }
total: { type: integer }
pages: { type: integer }
Probleme:
type: object
description: Erreur au format de la RFC 9457
properties:
type: { type: string, format: uri }
title: { type: string }
status: { type: integer }
detail: { type: string }
instance: { type: string }
responses:
NonAuthentifie:
description: Jeton absent, invalide ou expiré
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Probleme'
Introuvable:
description: Fiche inexistante ou appartenant à un autre visiteur
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Probleme'
Conflit:
description: Une fiche existe déjà pour ce mois
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Probleme'
Invalide:
description: Données invalides
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Probleme'
Ce qu'il faut remarquer :
serversdonne l'adresse de base : le chemin/fichesse lithttps://api.gsb.example/v1/fiches.securityau premier niveau impose le jeton à toutes les opérations. Le schémajeton, défini danscomponents.securitySchemes, est une authentification HTTP de typebeareravec un JWT (chapitre 9). Une route publique annulerait cette règle avecsecurity: [].- Les paramètres précisent leur emplacement (
in: querypour la chaîne de requête,in: pathpour un segment de l'URI comme{id},in: headerpour un en-tête), leur type et leurs contraintes :limitevaut 20 par défaut et 100 au maximum, comme décidé au chapitre 6. requestBodydécrit le corps attendu ;responsesdécrit chaque code possible, avec son corps et ses en-têtes (leLocationdu201).$refréutilise un élément decomponents: le schémaFicheest écrit une fois et utilisé par trois réponses ; les erreurs401,404,409et422partagent le schémaProblemede la RFC 9457, avec le typeapplication/problem+json.readOnly: truesignale un champ que le serveur renvoie mais que le client n'envoie jamais (l'id).type: [number, 'null']est la syntaxe d'OpenAPI 3.1 (héritée de JSON Schema) pour un champ qui peut valoirnull. En OpenAPI 3.0, on écrivaitnullable: true, qui n'existe plus en 3.1.additionalProperties: falsesurNouvelleFicherefuse tout champ inattendu : la liste blanche, encore.
Afficher la documentation : Swagger UI, Redoc, Scalar
Le document OpenAPI n'est pas fait pour être lu tel quel par les développeurs qui utilisent l'API. Des outils le transforment en pages web :
| Outil | Ce qu'il produit |
|---|---|
| Swagger UI | La documentation interactive classique : chaque opération se déplie, et le bouton Try it out envoie une vraie requête |
| Redoc | Une documentation en trois colonnes, très lisible, sans bouton d'essai dans sa version libre |
| Scalar | Une documentation interactive récente, avec un client HTTP intégré et des exemples de code dans plusieurs langages |
La plupart des frameworks savent servir l'une de ces pages à partir du document OpenAPI, souvent à l'adresse /docs ou /swagger.
La description d'abord, ou le code d'abord ?
Deux façons de produire le document OpenAPI :
La description d'abord (design-first, ou API-first). On écrit le document OpenAPI avant le code, on le fait relire par l'équipe du front et par les utilisateurs de l'API, puis on code l'API pour qu'elle le respecte. Avantages : le contrat est discuté avant d'être figé dans le code, le front peut commencer tout de suite avec un serveur simulé, et les erreurs de conception coûtent moins cher à corriger. C'est l'approche recommandée pour une API utilisée par d'autres équipes ou d'autres entreprises.
Le code d'abord (code-first). On écrit l'API, puis on génère le document à partir du code, de ses annotations ou de ses commentaires. Avantage : le document ne peut pas diverger du code. Inconvénient : la conception se fait au fil du code, sans relecture du contrat.
| Langage ou framework | Génération du document OpenAPI |
|---|---|
| Node.js avec Express | swagger-jsdoc, qui lit des commentaires au-dessus des routes, ou un schéma de validation (Zod) converti en OpenAPI |
| Node.js avec Fastify | @fastify/swagger, à partir des schémas JSON déclarés sur chaque route |
| PHP sans framework | swagger-php, qui lit des attributs PHP 8 placés sur les contrôleurs |
| PHP avec Laravel | Des paquets comme Scramble, qui analysent le code |
| C# avec ASP.NET Core | Le paquet Microsoft.AspNetCore.OpenApi, intégré aux modèles de projet depuis .NET 9 (Swashbuckle était utilisé auparavant) |
| Python avec FastAPI | Natif : le document est déduit des types Python, et Swagger UI est servi à /docs |
Pour ton projet de BTS, les deux approches se défendent. L'important, devant le jury de l'E6, est que la documentation existe et soit à jour : montrer Swagger UI, essayer une route en direct et expliquer un schéma, c'est une démonstration convaincante de la compétence « rédiger la documentation technique ».
Les autres outils de l'écosystème
- Les éditeurs : Swagger Editor en ligne, ou l'extension OpenAPI de VS Code, qui valident le document au fil de la frappe et proposent l'autocomplétion.
- Les linters, comme Spectral : ils vérifient des règles de style (URI en minuscules, chaque opération a une description, chaque réponse d'erreur utilise
application/problem+json) et s'intègrent à l'intégration continue. - Les serveurs simulés, comme Prism : ils répondent aux requêtes en suivant les exemples et les schémas du document.
- Les générateurs de code, comme OpenAPI Generator : un client dans plus de quarante langages, ou un squelette de serveur.
- Les tests de contrat : des outils comme Schemathesis génèrent des requêtes à partir du document et vérifient que les réponses de l'API respectent les schémas (chapitre 11).
Le README de l'API
Le document OpenAPI décrit les routes. Un développeur qui découvre l'API a aussi besoin d'un README court qui répond aux questions que le document ne traite pas :
- À quoi sert l'API, pour qui.
- Comment obtenir un accès (un compte, une clé, un jeton) et s'authentifier, avec un exemple
curlcomplet. - L'adresse de base de chaque environnement.
- Les conventions communes : pagination, format des dates, format des erreurs, limites de débit.
- Le lien vers la documentation interactive et vers le document OpenAPI.
- La politique de versionnement et la liste des changements (changelog).
- Comment lancer l'API en local, pour les développeurs de l'équipe.
Exercices
Exercice 1 sur 4 Lire un document OpenAPI Réussi À revoir
À partir de l'extrait du document d'AppliFrais, réponds :
- Quelle est l'URL complète pour lire la fiche 12 en production ?
- Quelles valeurs le paramètre
etatpeut-il prendre ? - Que se passe-t-il si un client envoie
limite=500à une API qui valide ses paramètres selon ce document ? - Un client envoie
POST /fichesavec{"mois":"2026-11","etat":"VA"}. La requête est-elle conforme ? Pourquoi est-ce important ? - Quelles réponses d'erreur sont documentées pour la création d'une fiche ? Laquelle manque, au vu de la colonne Rôle du chapitre 6 ?
Voir la réponseMasquer la réponse de l'exercice 1
https://api.gsb.example/v1/fiches/12(l'adresse du serveur, puis le chemin avecidremplacé par 12).CR,CL,VA,MP(l'énumération du schémaEtat).- Le paramètre viole
maximum: 100: l'API répond par une erreur de validation (400ou422), ou plafonne à 100 si c'est le comportement choisi et documenté. - Non :
NouvelleFichedéclareadditionalProperties: falseet seulmoisest permis. Si l'API acceptaitetat, un visiteur pourrait créer directement une fiche validée : c'est l'affectation de masse, une faille de contrôle d'accès aux propriétés (BOPLA). 401,409et422. Il manque au moins400(corps illisible),403(un comptable qui tenterait de créer une fiche, si la règle l'interdit) et429si une limite de débit s'applique.
Tu avais trouvé ?
Exercice 2 sur 4 Écrire une opération Réussi À revoir
Ajoute au document l'opération qui supprime une fiche : DELETE /fiches/{id}, permise seulement pour une fiche à l'état CR. Elle répond 204 sans corps, et peut renvoyer 401, 404 et 409. Réutilise les éléments de components.
Voir la réponseMasquer la réponse de l'exercice 2
Sous le chemin /fiches/{id}, qui déclare déjà le paramètre id pour toutes ses opérations :
delete:
tags: [Fiches]
summary: Supprimer une fiche en cours de saisie
operationId: supprimerFiche
responses:
'204':
description: Fiche supprimée
'401':
$ref: '#/components/responses/NonAuthentifie'
'404':
$ref: '#/components/responses/Introuvable'
'409':
description: La fiche n'est plus à l'état CR
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Probleme'
La réponse 204 n'a pas de clé content : elle n'a pas de corps. On ne réutilise pas la réponse Conflit, dont la description parle d'une fiche existante pour le mois : une description fausse dans une documentation est pire qu'une absence.
Tu avais trouvé ?
Exercice 3 sur 4 Trouver les erreurs Réussi À revoir
Ce fragment se veut de l'OpenAPI 3.1. Trouve quatre erreurs.
paths:
/visiteurs/{id}:
get:
parameters:
- name: id
in: query
schema: { type: integer }
responses:
200:
description: Le visiteur
content:
application/json:
schema:
type: object
properties:
telephone:
type: string
nullable: true
Voir la réponseMasquer la réponse de l'exercice 3
idfigure dans le chemin ({id}) : il doit être déclaréin: path, pasin: query.- Un paramètre de chemin doit avoir
required: true. - Les codes de réponse sont des clés de type chaîne : on écrit
'200'entre apostrophes. Sans elles, YAML lit le nombre 200, que beaucoup d'outils refusent. nullable: truen'existe plus en OpenAPI 3.1 : on écrittype: [string, 'null'].
On peut ajouter qu'aucune réponse d'erreur n'est documentée (401, 404).
Tu avais trouvé ?
Exercice 4 sur 4 Description d'abord ou code d'abord ? Réussi À revoir
Pour chaque projet, dis quelle approche tu choisis et pourquoi.
- Une API que le laboratoire GSB va ouvrir à trois agences de voyage partenaires, qui développeront leur propre client.
- Une petite API interne, codée par toi seul, utilisée par ton propre front React.
Voir la réponseMasquer la réponse de l'exercice 4
- La description d'abord : le contrat engage des entreprises extérieures. Il faut le faire relire et valider avant de coder, fournir tôt un serveur simulé pour que les partenaires avancent, et éviter tout changement incompatible une fois publié.
- Le code d'abord est acceptable : une seule personne, un seul client, des évolutions rapides. Générer le document depuis le code garantit qu'il reste à jour, et il servira de documentation pour le jury et pour la maintenance.
Tu avais trouvé ?