Ressources · 33
Faire évoluer les contrats API sans perdre les clients
Décrire requêtes, réponses et erreurs, tester les versions et rendre les webhooks rejouables.
· 9 min
Ce que ce guide permet
- Écrire le contrat
- Rendre les erreurs claires
- Tester la compatibilité
- Préparer le retrait
Contrôle express
- Quels clients consomment chaque opération ?
- Les réponses d’erreur ont-elles un type stable et un statut cohérent ?
- Un ancien client tolère-t-il le changement ?
- Les retries créent-ils des doublons ?
- Comment annoncer et vérifier un retrait ?
Méthode pas à pas
- 01
Inventorier les consommateurs
Lister opérations, clients, usages, versions et responsables. Distinguer usages observés et supposés avant de modifier un champ.
Livrable : carte des dépendances du contrat.
- 02
Décrire requêtes et réponses
Tenir une description OpenAPI avec schémas, exemples réalistes, réponses de succès et d’échec. Vérifier qu’elle reflète le service exécuté.
Livrable : contrat versionné et tests de conformité.
- 03
Stabiliser les erreurs
Utiliser les statuts HTTP selon leur sens et, lorsque pertinent, un type de problème documenté selon RFC 9457. Ne pas divulguer de secret dans les détails.
Livrable : catalogue d’erreurs testées.
- 04
Qualifier le changement
Examiner champs obligatoires, types, valeurs, comportements, délais et pagination. Tester les anciens clients contre la nouvelle version sur des cas représentatifs.
Livrable : matrice de compatibilité et décision de version.
- 05
Fiabiliser les notifications
Pour les webhooks, prévoir authentification, ordre non garanti, doublons et retries. Rendre le traitement idempotent quand une répétition est possible.
Livrable : scénario de replay et accusé de réception.
- 06
Déployer avec sortie claire
Publier notes de migration, période de coexistence et contacts. Mesurer l’usage ancien, puis retirer une version seulement après vérification des consommateurs concernés.
Livrable : plan de migration et preuves de retrait.
Indicateurs de pilotage
| Indicateur | Ce qu’il mesure | Première action |
|---|---|---|
| Contrat | Opérations décrites et conformes au service | Corriger les divergences |
| Compatibilité | Clients représentatifs testés avant changement | Ajouter les cas oubliés |
| Erreurs | Types documentés sans donnée sensible | Réviser messages et schémas |
| Migration | Usage des versions anciennes avec propriétaire | Accompagner les clients restants |
Erreurs fréquentes
- Croire qu’un numéro de version suffit à préserver la compatibilité
- Documenter seulement les réponses 200
- Envoyer une trace interne ou un secret dans une erreur
- Supposer qu’un webhook arrive une seule fois et dans l’ordre
Questions fréquentes
OpenAPI remplace-t-il un test ?
Non. La description doit être comparée aux réponses réelles et aux besoins des consommateurs.
Faut-il toujours créer une nouvelle version ?
Non. La décision dépend de l’effet sur les clients et du comportement du contrat ; les changements incompatibles exigent un plan explicite.
Pourquoi des erreurs typées ?
Elles donnent aux clients une façon stable de distinguer les problèmes et de choisir une action.
ETag, cache HTTP et modifications concurrentes
Deux clients, trois versions : rendre un conflit visible
Dans ce scénario fictif, A et B travaillent sur la même représentation. Le serveur émet un ETag fort pour chaque nouvelle version enregistrée. If-Match permet de refuser une modification fondée sur une version qui n’est plus courante. Les cinq étapes sont des extraits explicatifs, sans connexion ni écriture réelle.
1. A lit la version initiale
A reçoit la représentation v1 et conserve "v1" avec les données qu’il modifie. Un tag copié sans sa représentation ne suffit pas à comprendre le conflit.
Extrait de requête
GET /demo/resource HTTP/1.1
Host: api.example.testExtrait de réponse
HTTP/1.1 200 OK
Date: Thu, 22 Oct 2026 00:00:00 GMT
Cache-Control: no-store
ETag: "v1"2. B enregistre avant A
B a lui aussi lu v1. Son PUT satisfait If-Match: "v1". La nouvelle représentation exacte est enregistrée sous "v2" ; le serveur répond ici 204 et fournit ce nouveau validateur.
Extrait de requête
PUT /demo/resource HTTP/1.1
Host: api.example.test
If-Match: "v1"Extrait de réponse
HTTP/1.1 204 No Content
Date: Thu, 22 Oct 2026 00:00:00 GMT
Cache-Control: no-store
ETag: "v2"3. A tente une écriture périmée
La version courante est maintenant v2. Le PUT de A porte encore If-Match: "v1" : 412, sans appliquer sa modification. Préserver son travail local pour permettre une comparaison.
Extrait de requête
PUT /demo/resource HTTP/1.1
Host: api.example.test
If-Match: "v1"Extrait de réponse
HTTP/1.1 412 Precondition Failed
Date: Thu, 22 Oct 2026 00:00:00 GMT
Cache-Control: no-store4. A relit et arbitre le conflit
A récupère v2 et compare la version lue, sa modification locale et la version courante. Une décision humaine ou une règle métier explicite choisit les changements à conserver.
Extrait de requête
GET /demo/resource HTTP/1.1
Host: api.example.testExtrait de réponse
HTTP/1.1 200 OK
Date: Thu, 22 Oct 2026 00:00:00 GMT
Cache-Control: no-store
ETag: "v2"5. A soumet une modification réconciliée
Après cette décision, A envoie la nouvelle proposition avec If-Match: "v2". Elle est acceptée dans ce scénario et crée v3. Une autre écriture concurrente pourrait encore imposer un nouveau cycle.
Extrait de requête
PUT /demo/resource HTTP/1.1
Host: api.example.test
If-Match: "v2"Extrait de réponse
HTTP/1.1 204 No Content
Date: Thu, 22 Oct 2026 00:00:00 GMT
Cache-Control: no-store
ETag: "v3"Extraits HTTP/1.1 fictifs : les corps de requête et de réponse sont omis, même lorsqu’ils seraient possibles. HEAD, 204 et 304 restent sans contenu de réponse. Le domaine api.example.test et la date fixe servent uniquement à la lecture du scénario.
Le protocole détecte une version périmée ; il ne décide pas comment fusionner les contenus. Documenter ce que l’interface conserve en cas de 412, qui arbitre le conflit et quand une nouvelle lecture est nécessaire. Le joker If-Match: * ne remplace pas la comparaison d’un ETag fort précis.
Explorer les comparaisons et le simulateur ETag →
Références primaires et limites
Les normes décrivent les conditions HTTP et le cache. Les dix cas, le laboratoire et le parcours à deux clients sont des illustrations originales. Les résultats affichés sont calculés par ce modèle local, pas des mesures du site ni de votre infrastructure.
- IETF · RFC 9110 — HTTP Semantics · June 2022 · STD 97
§8.8.3.2; §13.1.1–13.1.2; §13.2; §15.3.5; §15.4.5 - IETF · RFC 9111 — HTTP Caching · June 2022 · STD 98
§4.1; §4.3; §5.2.2.4–5.2.2.5; §5.2.2.7 - IETF · RFC 6585 — Additional HTTP Status Codes · April 2012 · Standards Track
§3 · 428 Precondition Required
Références relues le
Retry-After : attente, budget et décision de reprise
Documenter le droit de répéter avant le délai de reprise
Retry-After explique une attente. Le contrat doit aussi définir les opérations rejouables, les clés, les versions et les états de résultat inconnu.
- Préciser si le champ contient une durée ou une date et comment le client traite les valeurs absentes ou invalides.
- Définir le pilote de reprise et le budget global pour éviter les tentatives superposées.
- Conserver les paramètres, le contexte et la fenêtre d’idempotence sans généraliser un contrat Stripe à toutes les API.
Explorer Retry-After et le modèle de reprise →
Références relues le · RFC 9110 · §9.2.2, §10.2.3 · RFC 6585 · §4 · AWS
OAuth/OIDC : audience et droits
Le contrat API doit nommer son audience et ses droits
Associez la route à une ressource attendue, à des scopes explicites et à une règle sur l’objet. La validation de signature ne suffit pas à établir ce contrat.
- Documenter les profils acceptés et distinguer access token, ID token et jeton opaque.
- Tester un jeton destiné à une autre API, un scope absent et un objet d’un autre locataire.
- Renvoyer un refus maîtrisé et conserver une trace expurgée de tout code ou jeton.
Voir les destinataires et les dix scénarios OAuth/OIDC →
Sources vérifiées le · IETF · RFC 9700 · OAuth 2.0 Security BCP · IETF · RFC 9068 · JWT access token profile · OpenID Foundation · OpenID Connect Core 1.0 · IETF · RFC 8725 · JWT Best Current Practices · IETF · RFC 6749 · OAuth 2.0 framework
Webhooks : signatures et rejeu
Le contrat doit définir exactement les octets authentifiés
Stripe et GitHub n’utilisent pas le même message HMAC. Documentez l’en-tête, le corps brut, les clés de confiance et la politique de fraîcheur sans généraliser un profil fournisseur.
- Tester un corps reformaté avec la signature originale.
- Séparer validation cryptographique, fraîcheur et reconnaissance des répétitions.
- Documenter acquittement durable et statut métier avec des preuves distinctes.
Voir les signatures, les empreintes et les dix cas webhooks →
Sources vérifiées le · Stripe · Webhooks · Stripe · Manage webhook endpoints · GitHub · Validating webhook deliveries · GitHub · Best practices for using webhooks · GitHub · Handling failed webhook deliveries
Erreurs API : types, statuts et preuves
Une erreur API utile relie un type stable, une occurrence et une action possible pour le client. La RFC 9457, publiée en juillet 2023 à la place de la RFC 7807, fournit un format commun. Elle ne transforme pas un message d’erreur en preuve d’autorisation, en consigne de rejeu ou en diagnostic de sécurité complet.
Cinq membres standard expliqués, six types de problèmes présents dans la copie IANA du 26 juin 2026 et huit cas fictifs de recette. Sources consultées le 23 octobre 2026. Les exemples et propositions de tests sont rédigés par CIE.
IETF · M. Nottingham, E. Wilde, S. Dalal · RFC 9457 · July 2023 · obsoletes RFC 7807 · §3.1, §3.2, §4.2, §5
Les cinq membres : rôle et lecture côté client
| Membre | Type attendu | Rôle | Question de recette |
|---|---|---|---|
| type | string · URI reference | Identifie le type de problème ; about:blank par défaut si absent. | Le client décide-t-il sur l’URI plutôt que sur un titre traduit ? |
| status | number · HTTP status | Rappelle le statut de cette occurrence ; valeur consultative. | Le corps et le statut de la réponse concordent-ils ? |
| title | string | Résumé lisible du type de problème. | La traduction change-t-elle seulement le texte destiné à la personne ? |
| detail | string | Explication lisible de cette occurrence. | Le client évite-t-il d’analyser la prose comme un code machine ? |
| instance | string · URI reference | Identifie une occurrence particulière. | La référence permet-elle un suivi sans exposer des détails internes ? |
Ces membres sont optionnels. Un membre d’un type JSON inattendu doit être ignoré par le consommateur. Une extension inconnue doit aussi pouvoir être ignorée. Le type de média JSON prévu est application/problem+json.
Atelier de lecture
Choisissez l’un des huit exemples puis modifiez, si utile, le statut HTTP supposé reçu. Le corps et le type de média restent ceux du cas choisi. L’atelier compare des faits limités ; il ne valide ni toutes les URI, ni le schéma métier, ni l’autorisation, ni le service réel. Le nom du type de média est comparé sans valider tous ses paramètres. La méthode HTTP et les autres règles de transport restent à vérifier.
Cet atelier utilise uniquement les exemples fictifs affichés ci-dessous. Les URI sont montrées comme texte : aucune n’est visitée ou résolue automatiquement. Aucun journal ou message de production n’est demandé.
Saisissez trois chiffres de 100 à 599.
Résultat de la comparaison
Content-Type: application/problem+json
Corps JSON fictif
{
"type": "https://api.example.net/problems/invalid-window",
"title": "Invalid time window",
"status": 422,
"detail": "The end must follow the start.",
"instance": "/problems/events/test-17",
"errors": [
{
"field": "end",
"code": "before-start"
}
]
}Six types enregistrés : conserver les URI et les références exactes
Ce registre de types communs n’est pas une liste de tous les problèmes qu’une API peut définir. N/A pour about:blank n’est pas un statut HTTP. Les trois entrées digest conservent ici une référence de type draft telle qu’elle figure dans la copie IANA ; cette mention n’est pas transformée en numéro de RFC finale.
IANA · HTTP Problem Types · Registry updated 2026-06-26 · consulted 2026-10-23
| URI de type et titre IANA | Statut recommandé | Référence telle que publiée | Date d’enregistrement |
|---|---|---|---|
about:blankSee HTTP Status Code | N/A | rfc · rfc9457 | 2023-05-02 |
https://iana.org/assignments/http-problem-types#dateDate Not Acceptable | 400 | rfc · rfc9458 · §6.5.2 | 2023-05-02 |
https://iana.org/assignments/http-problem-types#ohttp-keyOblivious HTTP key configuration not acceptable | 400 | rfc · rfc9458 · §5.3 | 2023-05-02 |
https://iana.org/assignments/http-problem-types#digest-unsupported-algorithmsUnsupported Hashing Algorithms | 400 | draft · RFC-ietf-httpapi-digest-fields-problem-types-06 · §3.1 | 2026-06-26 |
https://iana.org/assignments/http-problem-types#digest-invalid-valuesInvalid Digest Values | 400 | draft · RFC-ietf-httpapi-digest-fields-problem-types-06 · §3.2 | 2026-06-26 |
https://iana.org/assignments/http-problem-types#digest-mismatched-valuesMismatched Digest Values | 400 | draft · RFC-ietf-httpapi-digest-fields-problem-types-06 · §3.3 | 2026-06-26 |
IANA · Protocol Registries licensing · CC0 1.0 · Revised 2021-11-10 · CIE commentary and fictional examples identified separately
Les huit cas et les décisions de recette
P01 · Un type métier explicite
HTTP 422 · Content-Type: application/problem+json
{
"type": "https://api.example.net/problems/invalid-window",
"title": "Invalid time window",
"status": 422,
"detail": "The end must follow the start.",
"instance": "/problems/events/test-17",
"errors": [
{
"field": "end",
"code": "before-start"
}
]
}Lecture : Le statut du corps et celui de la réponse concordent. Le tableau errors est une extension propre à cet exemple, pas un membre standard universel.
Test à ajouter : Tester le code stable before-start et l’association au champ ; ne pas analyser le texte detail pour automatiser la correction.
P02 · Type et status absents
HTTP 404 · Content-Type: application/problem+json
{
"title": "Ressource introuvable",
"instance": "/support/test-18"
}Lecture : Les cinq membres ne forment pas une liste de champs tous obligatoires. Sans type exploitable, about:blank s’applique ; le statut HTTP reçu reste 404.
Test à ajouter : Vérifier que le client accepte cette réponse minimale et un titre localisé, sans inventer un statut stocké dans le corps.
P03 · Le corps annonce 403 mais HTTP indique 200
HTTP 200 · Content-Type: application/problem+json
{
"type": "https://api.example.net/problems/access-denied",
"title": "Access denied",
"status": 403
}Lecture : Il existe un écart entre deux couches. Le statut conseillé dans le JSON ne remplace pas celui reçu par les clients HTTP génériques.
Test à ajouter : Examiner la génération et les intermédiaires ; conserver les deux valeurs plutôt que déclarer la requête réussie à partir du seul HTTP 200.
P04 · Cinq membres du mauvais type JSON
HTTP 422 · Content-Type: application/problem+json
{
"type": 17,
"title": false,
"status": "422",
"detail": [
"Invalid"
],
"instance": null,
"traceRef": "test-19"
}Lecture : type, title, status, detail et instance sont de types inattendus. La règle de consommation de la RFC est de les ignorer, sans convertir automatiquement la chaîne "422" en nombre.
Test à ajouter : Signaler le défaut du producteur et tester un client tolérant. traceRef reste une extension ; about:blank est le type par défaut après ignorance de type.
P05 · URI relative et extensions inconnues
HTTP 409 · Content-Type: application/problem+json
{
"type": "/problems/conflict",
"title": "Version conflict",
"status": 409,
"instance": "?case=test-20",
"requestRef": "test-20",
"retryable": false
}Lecture : Le type commence par un chemin relatif et instance par une référence de requête. L’atelier les affiche sans les résoudre ; la base du document réel serait nécessaire.
Test à ajouter : Définir la base dans le contrat, préférer un type absolu et tester l’ignorance des extensions non reconnues. Le seul mot retryable ne donne pas un droit universel de rejeu.
P06 · Une URI enregistrée à comparer exactement
HTTP 400 · Content-Type: application/problem+json
{
"type": "https://iana.org/assignments/http-problem-types#date",
"title": "Date Not Acceptable",
"status": 400,
"instance": "/support/test-21"
}Lecture : Ce type figure dans la copie IANA. Son statut recommandé est 400. Une comparaison sur le seul suffixe #date ou sur title ne suffit pas à identifier le type.
Test à ajouter : Renvoyer le client vers la définition de ce type dans le contexte applicable ; l’enregistrement ne certifie pas l’implémentation.
P07 · Un objet ressemblant, un autre type de média
HTTP 503 · Content-Type: application/json
{
"type": "https://api.example.net/problems/maintenance",
"title": "Service maintenance",
"status": 503,
"detail": "Try the documented recovery path."
}Lecture : Le corps ressemble à un Problem Details, mais le nom du type de média reçu est application/json. Une lecture structurelle ne vérifie pas la négociation ou le contrat de réponse.
Test à ajouter : Comparer Content-Type, documentation et client. Ne pas transformer cet exemple en règle de relance : Retry-After et sécurité du rejeu restent des contrôles séparés.
P08 · Un nombre ne suffit pas à faire un statut HTTP
HTTP 400 · Content-Type: application/problem+json
{
"type": "about:blank",
"title": "Bad Request",
"status": 700,
"instance": "/support/test-22"
}Lecture : Le membre status est bien un nombre JSON, mais 700 est hors de la plage des codes HTTP à trois chiffres de 100 à 599. Ce défaut est distinct du mauvais type JSON.
Test à ajouter : Réparer la génération et conserver le statut HTTP reçu, ici 400. Un test de type de données seul aurait manqué cette anomalie.
IETF · RFC 9110 · HTTP Semantics · June 2022 · §6.4.1, §15, §15.3.6 · status range and response content
Faire évoluer une erreur sans casser le contrat
| Changement proposé | Risque concret pour le client | Essai CIE recommandé |
|---|---|---|
| Traduire title ou detail | Un client qui analyse la phrase peut prendre une autre branche. | Garder le même type et rejouer deux langues avec le même résultat machine attendu. |
| Changer l’URI type | Une nouvelle identité de problème peut remplacer l’ancienne. | Tester les consommateurs existants et documenter migration, tolérance et repli. |
| Ajouter une extension | Un client trop strict peut rejeter une réponse auparavant lue. | Ajouter un membre inconnu au test du consommateur et vérifier l’ignorance prévue. |
| Retirer ou renommer une extension métier | Une action cliente peut dépendre du champ documenté. | Vérifier le contrat propre au type, les usages observés et le plan de compatibilité. |
| Modifier le statut HTTP | Proxy, cache ou SDK peuvent traiter autrement la réponse. | Comparer réponse brute et corps, avec le chemin de génération et les intermédiaires. |
| Ajouter du détail technique | Chemins internes, secrets ou données de compte peuvent être exposés. | Tester le message public, la référence d’occurrence et les journaux à accès contrôlé séparément. |
Conservez un exemple de réponse brute par cas de refus, sa définition de type, le résultat du test du client et la version du contrat. Une cohérence entre deux nombres ne démontre pas que le serveur a choisi le bon refus ni qu’un nouvel essai est sans effet de bord.
Provenance et limites de cette copie
IANA · 6 types · registry 2026-06-26 · reviewed 2026-10-23 · CC0 1.0
IANA XML SHA-256: 2b6a7ae5a63a5f6afccd8884658393d5268d16b63d624e883fd5fe5dcf5aa57b
RFC 9457 TXT SHA-256: f2b3db92fb0bf3489cb3841a0da0c0d88dff40797b64d40b6123085183886c7b
GraphQL : erreurs, listes et données partielles
Contrat GraphQL : l’erreur n’efface pas toujours les données
Définir ce que le client peut conserver quand un résolveur échoue : le contrat comprend la forme de data, la portée de null et le chemin d’erreur.
- Comparer data absente, data partielle et data: null.
- Conserver le chemin original, même si un parent devient null.
- Tester les droits, les quotas et les états d’interface séparément.
Explorer les réponses GraphQL et leur nullabilité →
GraphQL Specification · September 2025 · Published edition · September 2025
API : dépréciation et retrait
Préparer le retrait au-delà du numéro de version
Une version publiée ne dit pas quand les consommateurs doivent migrer. Relier les annonces de dépréciation, les dates de retrait et les tests des clients existants.
- Distinguer Deprecation, Sunset et les liens vers les politiques.
- Vérifier que Sunset n’est pas antérieur à Deprecation lorsque les deux dates sont fournies.
- Mesurer les consommateurs restants et valider le remplacement avant une bascule.
Comparer les annonces de dépréciation et de retrait →
IETF · RFC 9745 · Deprecation · Standards Track · March 2025 · §2–5 · consulted 2026-10-23
IETF · RFC 8594 · Sunset · Informational · May 2019 · §3–5, §8 · consulted 2026-10-23
Références officielles
Date de consultation des références : . La méthode et la fiche de travail proposent des contrôles à adapter à votre contexte ; elles ne constituent pas une certification.
- OpenAPI Specification 3.1.1
- IETF RFC 9457 — Problem Details for HTTP APIs
- IETF · RFC 9110 — HTTP Semantics
- IETF · RFC 9111 — HTTP Caching
- IETF · RFC 6585 — Additional HTTP Status Codes
- IETF · RFC 9700 · OAuth 2.0 Security BCP
- IETF · RFC 9068 · JWT access token profile
- OpenID Foundation · OpenID Connect Core 1.0
- IETF · RFC 8725 · JWT Best Current Practices
- IETF · RFC 6749 · OAuth 2.0 framework
- Stripe · Webhooks
- Stripe · Manage webhook endpoints
- GitHub · Validating webhook deliveries
- GitHub · Best practices for using webhooks
- GitHub · Handling failed webhook deliveries
- IETF · RFC 9745 · Deprecation
- IETF · RFC 8594 · Sunset
- GraphQL Specification · September 2025
- IANA · HTTP Problem Types
- IETF · RFC 9110 · HTTP Semantics
- AWS Builders’ Library · Timeouts, retries, and backoff with jitter
- Stripe · Idempotent requests
- Stripe · API v2 overview — Idempotency
- Stripe · Advanced error handling
