Aller au contenu
Réussir mon BTS SIO

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

Par exemple

Chapitre 825 minDébutant

Documenter une API REST avec OpenAPI 3.1

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • lire et écrire un document OpenAPI 3.1 ;
  • documenter les erreurs, l'authentification et des exemples ;
  • valider le document avec un linter ;
  • garder la documentation fidèle au code.

3 exercices corrigés1 schéma

Documenter une API REST, c'est décrire, pour les développeurs des applications qui l'appellent, chaque route : son adresse, ses paramètres, le corps attendu, les réponses possibles avec leurs codes, et la façon de s'authentifier. Le standard pour le faire est OpenAPI, un document YAML ou JSON lisible à la fois par les humains et par les outils. Ce chapitre se concentre sur l'écriture d'une documentation utile : un exemple complet commenté, des descriptions, des exemples et des erreurs bien documentés, la validation avec un linter et le maintien à jour. La structure détaillée d'OpenAPI et ses outils d'affichage sont dans le cours API REST.

Ce qu'un développeur attend d'une documentation d'API

Mets-toi à la place de Chloé, qui doit brancher l'intranet sur l'API du parc. Elle a besoin de savoir :

  1. à quoi sert l'API et ce qu'elle ne fait pas ;
  2. comment s'authentifier, avec un exemple ;
  3. l'adresse de base de chaque environnement ;
  4. pour chaque route : la méthode, le chemin, les paramètres (obligatoires ou non, type, limites), le corps attendu et les réponses, succès comme erreurs ;
  5. le format des erreurs, pour les afficher correctement à l'utilisateur ;
  6. des exemples réalistes, qu'elle peut copier pour tester ;
  7. ce qui change d'une version à l'autre et ce qui va disparaître.

Une documentation qui ne décrit que les cas où tout se passe bien laisse le développeur deviner le reste, et il devine mal : c'est le 422 non documenté qui finit en « Erreur inconnue » affichée à l'utilisateur.

OpenAPI en 2026 : quelle version ?

VersionPublicationÀ retenir
Swagger 2.02014Ancien nom du format ; encore présent dans de vieux projets (swagger: "2.0")
OpenAPI 3.02017Premier nom « OpenAPI » ; components, requestBody, plusieurs serveurs
OpenAPI 3.12021 ; correctif 3.1.2 en septembre 2025Les schémas sont du JSON Schema 2020-12 complet ; la version la mieux prise en charge par les outils
OpenAPI 3.2Septembre 2025Compatible avec la 3.1 ; ses nouveautés sont adoptées progressivement par les outils

Ce chapitre, comme le cours API REST, écrit de l'OpenAPI 3.1 (openapi: 3.1.2). Ne confonds pas ce numéro, qui est celui de la spécification, avec info.version, qui est celui de ton API.

La structure d'un document OpenAPI

openapiversion d'OpenAPIinfotitre, version de l'APIserversadresses de basesecurityauthentificationtagsgroupes de routespathsles routescomponentséléments réutilisables/postes/{numInventaire}getparametersnumInventaire$refresponses'200'$ref'401'$ref'404'$refsecuritySchemesjetonschemasNumInventairePosteProblemeresponsesNonAuthentifieIntrouvable
Les routes décrivent chaque opération ; components décrit une seule fois ce qu'elles réutilisent, par des références $ref

Le principe à retenir : paths décrit les opérations, components décrit une seule fois ce qu'elles partagent (les formes de données, les réponses d'erreur, l'authentification), et chaque opération y renvoie par une référence $ref. Quand le schéma d'un poste change, on le modifie à un seul endroit.

L'exemple complet : l'API du parc de Sorbier

L'API du parc expose l'inventaire des postes de la base parc et permet d'ouvrir un ticket. Son document complet fait 255 lignes ; il est téléchargeable ici et passe la validation sans erreur. Voici ses parties, commentées.

L'en-tête : de quoi parle-t-on, où, comment s'authentifier.

yaml
openapi: 3.1.2
info:
  title: API du parc de Sorbier Logistique
  version: 1.2.0
  summary: Inventaire des postes et tickets d'assistance du service informatique.
  description: |
    API interne du service informatique de Sorbier Logistique. Elle expose
    l'inventaire des postes de travail et permet d'ouvrir des tickets.

    - Les dates suivent la norme ISO 8601 (`2026-11-08`).
    - Les erreurs suivent le format de la RFC 9457 (`application/problem+json`).
    - L'API n'est joignable que depuis le réseau interne.
  contact:
    name: Service informatique de Sorbier Logistique
    email: informatique@sorbier.example
  license:
    name: Usage interne, propriété de Sorbier Logistique
    identifier: LicenseRef-Sorbier-interne
servers:
  - url: https://parc.ad.sorbier.example/api/v1
    description: Production, sur le réseau interne
  - url: http://localhost:3000/api/v1
    description: Poste du développeur
security:
  - jeton: []
tags:
  - name: Postes
    description: Inventaire des postes de travail
  - name: Tickets
    description: Demandes d'assistance
  • info.version vaut 1.2.0, comme la dernière version du CHANGELOG (chapitre 5) : la documentation dit de quelle version elle parle.
  • La description donne les conventions communes à toutes les routes (dates, erreurs, accès) une seule fois, au lieu de les répéter partout. Elle accepte le Markdown.
  • license.identifier attend un identifiant de licence au format SPDX (MIT, Apache-2.0…) ; pour une licence maison, SPDX prévoit la forme LicenseRef-….
  • security au premier niveau exige le jeton pour toutes les opérations ; une route publique l'annulerait avec security: [].

Une opération de lecture.

yaml
  /postes/{numInventaire}:
    get:
      tags: [Postes]
      summary: Lire un poste
      description: Renvoie un poste à partir du numéro collé sur l'unité centrale.
      operationId: lirePoste
      parameters:
        - name: numInventaire
          in: path
          required: true
          description: Numéro d'inventaire du poste.
          schema:
            $ref: '#/components/schemas/NumInventaire'
          example: PC-0008
      responses:
        '200':
          description: Le poste demandé.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Poste'
        '401':
          $ref: '#/components/responses/NonAuthentifie'
        '404':
          $ref: '#/components/responses/Introuvable'
  • summary est un titre court qui commence par un verbe ; description ajoute ce qu'un développeur doit savoir.
  • Le paramètre de chemin est déclaré in: path et required: true : un paramètre de chemin est toujours obligatoire.
  • Les codes de réponse sont écrits entre apostrophes ('200') : ce sont des clés de type chaîne.
  • Les deux erreurs possibles sont documentées, en renvoyant vers des réponses communes.

Un paramètre obsolète. La version 1.2.0 a remplacé le paramètre stock de GET /postes par enStock. L'ancien reste documenté, marqué deprecated, tant qu'il fonctionne :

yaml
        - name: enStock
          in: query
          description: "`true` : seulement les postes affectés à personne."
          schema:
            type: boolean
        - name: stock
          in: query
          deprecated: true
          description: Remplacé par `enStock` ; sera supprimé dans la version 2.0.0.
          schema:
            type: boolean

Les outils d'affichage signalent un paramètre deprecated : le développeur sait qu'il doit migrer, et quand. La même information figure dans la rubrique Obsolète du CHANGELOG.

Une opération d'écriture.

yaml
  /tickets:
    post:
      tags: [Tickets]
      summary: Ouvrir un ticket
      description: |
        Ouvre un ticket au nom de l'utilisateur authentifié. Le ticket est créé
        avec le statut `ouvert` ; sans priorité, il reçoit la priorité `normale`.
      operationId: ouvrirTicket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NouveauTicket'
            example:
              titre: Ventilateur bruyant
              priorite: basse
              numInventaire: PC-0008
      responses:
        '201':
          description: Ticket créé. L'en-tête `Location` donne son adresse.
          headers:
            Location:
              description: URI du nouveau ticket.
              schema:
                type: string
              example: /api/v1/tickets/15
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Ticket'
        '401':
          $ref: '#/components/responses/NonAuthentifie'
        '422':
          $ref: '#/components/responses/Invalide'
  • La description donne les règles métier que le schéma ne dit pas : le demandeur est l'utilisateur du jeton (pas un champ du corps), le statut de départ, la priorité par défaut. Ce sont les colonnes statut et priorite de la table ticket, avec leurs valeurs par défaut.
  • L'example du corps est réaliste : c'est le ticket 6 de la base parc. Un exemple vraisemblable aide plus qu'un string répété.
  • La réponse 201 documente l'en-tête Location et le corps renvoyé.

Les composants réutilisables (extrait : les champs marque et modele du poste, et les autres réponses d'erreur, sont dans le fichier complet).

yaml
components:
  securitySchemes:
    jeton:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Jeton délivré par le service d'authentification de l'intranet.
  schemas:
    NumInventaire:
      type: string
      pattern: '^PC-[0-9]{4}$'
      examples: [PC-0008]
    Poste:
      type: object
      required: [numInventaire, type, marque, modele, dateAchat, prixAchat, affecteA]
      properties:
        numInventaire:
          $ref: '#/components/schemas/NumInventaire'
        type:
          $ref: '#/components/schemas/TypePoste'
        dateAchat:
          type: string
          format: date
        prixAchat:
          type: number
          minimum: 0
          description: Prix d'achat en euros.
        affecteA:
          type: [string, 'null']
          format: email
          description: Adresse du salarié qui utilise le poste ; `null` pour un poste en stock.
    NouveauTicket:
      type: object
      required: [titre]
      additionalProperties: false
      properties:
        titre:
          type: string
          minLength: 5
          maxLength: 100
        priorite:
          $ref: '#/components/schemas/Priorite'
        numInventaire:
          $ref: '#/components/schemas/NumInventaire'
  responses:
    Invalide:
      description: Le corps de la requête ne respecte pas le schéma.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Probleme'
          example:
            title: Données invalides
            status: 422
            detail: Le titre doit contenir au moins 5 caractères.
  • NumInventaire est défini une fois avec son motif (PC- et quatre chiffres) et réutilisé par le paramètre de chemin, le poste et le nouveau ticket.
  • type: [string, 'null'] est la syntaxe d'OpenAPI 3.1 pour un champ qui peut valoir null (l'ancien nullable: true de la 3.0 n'existe plus). Ici, null a un sens métier, expliqué dans la description : le poste est en stock.
  • Les unités sont dites (Prix d'achat en euros) : un nombre sans unité est une source d'erreur.
  • additionalProperties: false refuse tout champ inattendu dans le corps : un client ne peut pas glisser un statut: resolu à la création.
  • Chaque réponse d'erreur a un exemple au format de la RFC 9457 : le développeur voit exactement ce qu'il recevra et peut afficher detail à l'utilisateur.

Écrire pour le développeur

À fairePourquoi
Un summary court qui commence par un verbeIl sert de titre dans la documentation affichée
Une description pour chaque paramètre, avec l'unité et les limiteslimite ou duree ne disent rien sans unité ni maximum
Les règles métier dans la description de l'opérationQui a le droit, ce qui est créé, les valeurs par défaut
Toutes les réponses d'erreur prévues, avec un exempleLe client sait quoi afficher, au lieu de « Erreur inconnue »
Des exemples tirés du jeu d'essaiRéalistes et cohérents entre eux
deprecated: true sur ce qui va disparaîtreLe développeur migre avant la rupture
info.version alignée sur le CHANGELOGOn sait de quelle version parle la documentation

Valider le document avec un linter

Un document OpenAPI est du code : une indentation décalée, une référence mal orthographiée, et les outils le refusent ou, pire, l'affichent à moitié. Un linter le vérifie. Redocly CLI fonctionne sans installation préalable, avec npx :

bash
npx @redocly/cli lint openapi.yaml

Sur le document de l'API du parc, il répond (extrait : l'outil affiche aussi les lignes concernées et un lien vers la règle) :

text
validating openapi.yaml...
[1] openapi.yaml:22:10 at #/servers/1/url

Server `url` should not point to example.com or localhost.

Warning was generated by the no-server-example.com rule.

Woohoo! Your API description is valid. 🎉
You have 1 warning.

Le document est valide ; l'avertissement vient d'une règle de style du jeu de règles recommandé, qui déconseille un serveur localhost dans une documentation publiée. Pour une documentation interne destinée aussi aux développeurs, ce serveur est voulu : on désactive la règle dans un fichier redocly.yaml placé à côté du document.

yaml
extends:
  - recommended
rules:
  no-server-example.com: off

Une vraie erreur bloque la validation. Si l'on se trompe dans le nom d'un composant (NouveauTickett), Redocly CLI signale Can't resolve $ref avec la ligne, et la validation échoue (Validation failed). Ajoute la commande de lint à ton pipeline d'intégration continue : une documentation cassée bloque la fusion (le cours Intégration continue).

D'autres outils font le même travail : Spectral (npx @stoplight/spectral-cli lint openapi.yaml, avec un fichier de règles .spectral.yaml), l'extension OpenAPI de VS Code, ou Swagger Editor en ligne, qui valide pendant la frappe.

Produire une page de documentation. Redocly CLI génère aussi une page HTML autonome, lisible par n'importe qui, à partir du même document :

bash
npx @redocly/cli build-docs openapi.yaml -o docs/api.html

Swagger UI, Redoc et Scalar, qui affichent une documentation interactive, sont comparés dans le cours API REST ; le cours Node.js montre comment servir Swagger UI depuis une API Express.

Garder la documentation fidèle au code

Une documentation d'API devient fausse dès qu'une route change sans elle. Trois habitudes l'en empêchent :

  • La même pull request modifie la route et son document OpenAPI ; le relecteur refuse l'une sans l'autre.
  • Le choix entre la description d'abord et le code d'abord : écrire le document avant le code pour une API utilisée par d'autres équipes, le générer à partir du code (annotations, schémas de validation) pour une petite API interne. Le cours API REST détaille les deux approches.
  • Des tests de contrat qui vérifient que les réponses réelles de l'API respectent les schémas du document : si l'API renvoie un champ absent de la documentation, le test échoue (tester une API).

Exercices

Exercice 1 sur 3 Lire la documentation Réussi À revoir

À partir des extraits de ce chapitre, réponds.

  1. Quelle est l'URL complète pour lire le poste PC-0008 en production ?
  2. Chloé envoie POST /tickets avec le corps {"titre": "Bruit"}. Que se passe-t-il, et que reçoit-elle ?
  3. Elle envoie {"titre": "Écran noir au démarrage"}, sans priorité. Quelle priorité aura le ticket, et où est-ce écrit ?
  4. Un ancien script appelle GET /postes?stock=true. Fonctionne-t-il encore ? Que doit faire son auteur ?
Voir la réponseMasquer la réponse de l'exercice 1
  1. https://parc.ad.sorbier.example/api/v1/postes/PC-0008 : l'adresse du serveur de production suivie du chemin, avec {numInventaire} remplacé.
  2. Le titre fait 5 caractères (« Bruit ») : il respecte minLength: 5. Le ticket est créé : réponse 201, avec l'en-tête Location et le ticket en JSON. Un titre de 4 caractères aurait donné une 422 au format application/problem+json.
  3. La priorité normale : c'est écrit dans la description de l'opération (« sans priorité, il reçoit la priorité normale »), pas dans le schéma. C'est exactement le genre de règle métier qu'une bonne description doit porter.
  4. Oui, pour l'instant : le paramètre stock est deprecated, pas supprimé. Il sera supprimé dans la version 2.0.0 : l'auteur du script doit remplacer stock par enStock dès maintenant.

Tu avais trouvé ?

Exercice 2 sur 3 Lire un rapport de linter Réussi À revoir

Un collègue a modifié le document et Redocly CLI affiche deux erreurs :

text
[1] openapi.yaml:80:7 at #/paths/~1postes~1{numInventaire}/get/parameters
The operation does not define the path parameter `{numInventaire}` expected by path `/postes/{numInventaire}`.

[2] openapi.yaml:111:15 at #/paths/~1tickets/post/requestBody/content/application~1json/schema
Can't resolve $ref

Explique chaque erreur et sa correction probable. Que signifie ~1 dans les chemins affichés ?

Voir la réponseMasquer la réponse de l'exercice 2
  1. Le chemin /postes/{numInventaire} contient un paramètre, mais l'opération ne déclare aucun paramètre in: path de ce nom : le collègue a probablement écrit in: query, ou supprimé required: true en même temps. Correction : in: path et required: true.
  2. La référence du corps de POST /tickets pointe vers un composant qui n'existe pas : une faute de frappe dans le nom (par exemple NouveauTickett). Correction : écrire exactement '#/components/schemas/NouveauTicket'.

~1 est l'encodage de la barre oblique / dans un pointeur JSON (RFC 6901) : #/paths/~1postes~1{numInventaire} désigne la clé /postes/{numInventaire} de paths, dont le nom contient des barres obliques.

Tu avais trouvé ?

Exercice 3 sur 3 Documenter une nouvelle route Réussi À revoir

Ajoute l'opération GET /tickets/{id} : elle renvoie un ticket (200, schéma Ticket). Un salarié ne lit que ses propres tickets ; un technicien les lit tous. Prévois les réponses 401 et 404. Attention : la réponse commune Introuvable parle d'un poste.

Voir la réponseMasquer la réponse de l'exercice 3
yaml
  /tickets/{id}:
    get:
      tags: [Tickets]
      summary: Lire un ticket
      description: |
        Renvoie un ticket. Un salarié ne lit que ses propres tickets ;
        un technicien du service informatique les lit tous.
      operationId: lireTicket
      parameters:
        - name: id
          in: path
          required: true
          description: Numéro du ticket.
          schema:
            type: integer
            minimum: 1
          example: 6
      responses:
        '200':
          description: Le ticket demandé.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Ticket'
        '401':
          $ref: '#/components/responses/NonAuthentifie'
        '404':
          description: Ticket inexistant, ou appartenant à un autre salarié.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Probleme'

La 404 est écrite sur place, avec une description juste : réutiliser Introuvable, qui annonce « Aucun poste ne porte ce numéro d'inventaire », mettrait une phrase fausse dans la documentation. Remarque aussi le choix de répondre 404, et non 403, quand un salarié demande le ticket d'un autre : l'API ne révèle pas l'existence d'un ticket qu'il n'a pas le droit de voir. Avec cette opération, le document passe toujours la validation de Redocly CLI.

Tu avais trouvé ?

Chapitre terminé !

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

chapitres
–
de série
–
du cours
–
Chapitre suivant : Documenter une infrastructure : schéma réseau, plan d'adressage, inventaireRevoir le sommaire du cours