Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

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.

VersionAnnéeCe qu'il faut savoir
Swagger 2.02014Encore croisée dans de vieux projets ; mot-clé swagger: "2.0"
OpenAPI 3.02017Refonte : components, requestBody, plusieurs serveurs
OpenAPI 3.12021Les 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.2Septembre 2025Compatible 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
openapiLa version de la spécification : 3.1.2
infoLe titre, la version de ton API (à ne pas confondre avec la précédente), une description
serversLes adresses de base : production, préproduction, poste local
securityL'authentification exigée par défaut pour toutes les opérations
tagsDes groupes pour ranger les opérations dans la documentation
pathsLes routes : pour chaque chemin, les opérations (get, post…) avec leurs paramètres, leur corps et leurs réponses
componentsLes é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.

yaml
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 :

  • servers donne l'adresse de base : le chemin /fiches se lit https://api.gsb.example/v1/fiches.
  • security au premier niveau impose le jeton à toutes les opérations. Le schéma jeton, défini dans components.securitySchemes, est une authentification HTTP de type bearer avec un JWT (chapitre 9). Une route publique annulerait cette règle avec security: [].
  • Les paramètres précisent leur emplacement (in: query pour la chaîne de requête, in: path pour un segment de l'URI comme {id}, in: header pour un en-tête), leur type et leurs contraintes : limite vaut 20 par défaut et 100 au maximum, comme décidé au chapitre 6.
  • requestBody décrit le corps attendu ; responses décrit chaque code possible, avec son corps et ses en-têtes (le Location du 201).
  • $ref réutilise un élément de components : le schéma Fiche est écrit une fois et utilisé par trois réponses ; les erreurs 401, 404, 409 et 422 partagent le schéma Probleme de la RFC 9457, avec le type application/problem+json.
  • readOnly: true signale 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 valoir null. En OpenAPI 3.0, on écrivait nullable: true, qui n'existe plus en 3.1.
  • additionalProperties: false sur NouvelleFiche refuse 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 :

OutilCe qu'il produit
Swagger UILa documentation interactive classique : chaque opération se déplie, et le bouton Try it out envoie une vraie requête
RedocUne documentation en trois colonnes, très lisible, sans bouton d'essai dans sa version libre
ScalarUne 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 frameworkGénération du document OpenAPI
Node.js avec Expressswagger-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 frameworkswagger-php, qui lit des attributs PHP 8 placés sur les contrôleurs
PHP avec LaravelDes paquets comme Scramble, qui analysent le code
C# avec ASP.NET CoreLe paquet Microsoft.AspNetCore.OpenApi, intégré aux modèles de projet depuis .NET 9 (Swashbuckle était utilisé auparavant)
Python avec FastAPINatif : 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 :

  1. À quoi sert l'API, pour qui.
  2. Comment obtenir un accès (un compte, une clé, un jeton) et s'authentifier, avec un exemple curl complet.
  3. L'adresse de base de chaque environnement.
  4. Les conventions communes : pagination, format des dates, format des erreurs, limites de débit.
  5. Le lien vers la documentation interactive et vers le document OpenAPI.
  6. La politique de versionnement et la liste des changements (changelog).
  7. 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 :

  1. Quelle est l'URL complète pour lire la fiche 12 en production ?
  2. Quelles valeurs le paramètre etat peut-il prendre ?
  3. Que se passe-t-il si un client envoie limite=500 à une API qui valide ses paramètres selon ce document ?
  4. Un client envoie POST /fiches avec {"mois":"2026-11","etat":"VA"}. La requête est-elle conforme ? Pourquoi est-ce important ?
  5. 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
  1. https://api.gsb.example/v1/fiches/12 (l'adresse du serveur, puis le chemin avec id remplacé par 12).
  2. CR, CL, VA, MP (l'énumération du schéma Etat).
  3. Le paramètre viole maximum: 100 : l'API répond par une erreur de validation (400 ou 422), ou plafonne à 100 si c'est le comportement choisi et documenté.
  4. Non : NouvelleFiche déclare additionalProperties: false et seul mois est permis. Si l'API acceptait etat, 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).
  5. 401, 409 et 422. Il manque au moins 400 (corps illisible), 403 (un comptable qui tenterait de créer une fiche, si la règle l'interdit) et 429 si 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 :

yaml
    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.

yaml
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
  1. id figure dans le chemin ({id}) : il doit être déclaré in: path, pas in: query.
  2. Un paramètre de chemin doit avoir required: true.
  3. 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.
  4. nullable: true n'existe plus en OpenAPI 3.1 : on écrit type: [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.

  1. Une API que le laboratoire GSB va ouvrir à trois agences de voyage partenaires, qui développeront leur propre client.
  2. 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
  1. 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é.
  2. 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é ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Consommer une API : fetch en JavaScript, PHP, Python et C#Revoir le sommaire du cours