Codes HTTP dans une API métier : que répondre quand on cherche un contact, et ce que coûte une erreur
Par Dorian Chávez · fondateur et PDG de Hábil ·
Quand répondre 200, 404, 400, 422, 409, 429 ou 503 à la recherche d'un contact, et ce que coûte un mauvais choix : relances, tableaux trompeurs et agents.
Un intégrateur de votre entreprise appelle une API pour chercher un contact à partir de son adresse e-mail. La réponse arrive avec le code 200 et un corps qui dit {"erreur": "contact introuvable"}. Le programme de l'intégrateur voit le 200, considère l'opération comme réussie et poursuit avec un contact qui n'existe pas. Personne ne s'en aperçoit jusqu'à ce que, des semaines plus tard, quelqu'un demande pourquoi il manque des commandes.
Rien n'a failli dans le réseau, ni dans le serveur, ni dans la base de données. C'est une décision d'apparence mineure qui a failli : quel numéro placer sur la première ligne de la réponse. Le code d'état est la seule chose que lisent, sans l'interpréter, les répartiteurs de charge, les tableaux de bord de supervision, les bibliothèques de nouvelle tentative, les passerelles et les programmes de vos partenaires. Une phrase dans le corps, une personne la comprend ; le numéro, tout le reste le comprend.
Cet article s'adresse aux CTO et aux responsables technologiques, et il est écrit pour qu'un directeur qui le parcourt rapidement retienne l'essentiel dans les tableaux et les conclusions de section. Il passe en revue les codes réellement utilisés dans une API métier à partir d'un seul exemple, chercher un contact, et explique quand chacun convient et ce que coûte une erreur. Les affirmations techniques s'appuient sur les standards HTTP et sont citées à la fin ; les critères de conception qui sont les nôtres sont signalés comme tels.
Une réponse HTTP comporte un code, des en-têtes et un corps. Le code et les en-têtes sont des signaux standardisés que de nombreux outils utilisent pour décider ; le corps est lu par une personne ou, si le contrat le définit, par un programme.
| Si la réponse est… | La famille est… | Règle générale |
|---|---|---|
| Un vrai succès | 2xx | Poursuivre |
| Une requête à corriger | 4xx | Corriger, s'identifier ou attendre selon le code et le contrat |
| Une défaillance du serveur ou d'une dépendance | 5xx | Ne réessayer que si l'opération est répétable ou si le contrat offre une idempotence vérifiable |
Les trois parties d'une réponse, et pourquoi le numéro commande
En HTTP/1.1, la réponse commence par une ligne d'état : le code et, facultativement, une formule ; en HTTP/2 et dans les versions ultérieures, le code voyage comme métadonnée de la réponse, avec la même signification. Le standard précise que ces formules ne sont que des recommandations : elles peuvent être remplacées par des équivalents locaux ou omises sans effet sur le protocole [1, §15.1]. Ce qui compte, c'est le numéro.
Le premier chiffre définit la famille, et le standard exige de tout client qu'il la comprenne, même s'il ne connaît pas le code exact [1, §15] :
| Famille | Signification dans le standard | Ce que cela dit à l'appelant |
|---|---|---|
2xx | La requête a été reçue, comprise et acceptée | Poursuivez |
3xx | Une autre action est nécessaire pour la mener à bien | Allez ailleurs ou réutilisez ce que vous avez |
4xx | La requête contient une erreur ou ne peut pas être satisfaite | Examinez le code précis et le contrat ; la répéter à l'identique n'aide presque jamais |
5xx | Le serveur a échoué à traiter une requête en apparence valide | Examinez le code précis et le contrat ; ne réessayez pas à l'aveugle une écriture non idempotente |
Une règle explique pourquoi bien choisir importe plus que choisir avec précision : un client qui reçoit un code qu'il ne reconnaît pas doit le traiter comme le x00 de sa famille. Le standard l'illustre lui-même : un hypothétique 471 se traite comme un 400 [1, §15]. Autrement dit, la famille est le minimum que toute machine va comprendre. Si vous placez un 200 sur une erreur, la famille que les machines lisent est celle du succès.
Les en-têtes complètent le numéro. Certains sont obligatoires avec certains codes : un 401 doit comporter WWW-Authenticate et un 405 doit comporter Allow avec les méthodes que la ressource accepte réellement [1, §15.5.2 et §15.5.6]. Quant au corps, nous le verrons plus loin, il convient qu'il ait un format standard, afin qu'un programme n'ait pas à deviner.
Parmi les réponses d'erreur de vos API actuelles, combien se distinguent d'un succès en ne regardant que la première ligne ?
Le cas : chercher un contact
Imaginez une API de contacts d'une entreprise, destinée à ses canaux et à ses partenaires. Elle offre deux façons de retrouver quelqu'un :
GET /contacts/{id} -> obtenir un contact par son identifiant
GET /contacts?email=ana@exemple.com -> chercher des contacts selon un critèreElles semblent identiques. Elles ne le sont pas, et cette différence entraîne déjà la première décision. Avec l'identifiant, l'appelant affirme : « ce contact existe ; donnez-le-moi ». Avec la recherche, il demande : « y en a-t-il un qui corresponde à ceci ? ». Et la bonne réponse à une question qui admet « aucun » n'est pas une erreur.
Un contrat de style OpenAPI, simplifié, pour avoir la carte complète avant de la parcourir :
paths:
/contacts/{id}:
get:
summary: Obtenir un contact par identifiant
responses:
"200": { description: Le contact }
"400": { description: L'identifiant n'a pas une forme valide }
"401": { description: Identification requise }
"403": { description: Identifié, mais sans autorisation }
"404": { description: N'existe pas, ou on ne révèle pas qu'il existe }
"410": { description: A existé et a été retiré définitivement }
"429": { description: Trop de requêtes ; voir Retry-After }
"503": { description: Service indisponible pour le moment ; voir Retry-After }
/contacts:
get:
summary: Chercher des contacts selon un critère
responses:
"200": { description: Liste des correspondances (peut être vide) }
"400": { description: Critère illisible ou paramètre inconnu }
"422": { description: Critère bien formé, mais que le métier n'admet pas }
post:
summary: Créer un contact
responses:
"201": { description: Créé ; l'en-tête Location pointe vers lui }
"409": { description: Il en existe déjà un qui entre en conflit avec celui-ci }
"422": { description: Données bien formées mais qui enfreignent une règle }Nous les parcourons de la moindre à la plus grande gravité, en commençant par ce qui se passe bien.
Si votre équipe devait dessiner cette carte pour l'API de vos clients ou de vos polices, tous s'accorderaient-ils sur le nombre de réponses différentes qu'elle doit pouvoir donner ?
Quand tout se passe bien : 200, et la tentation de l'utiliser pour tout
La recherche sans correspondance mérite de l'attention. Une requête comme celle-ci :
curl -i "https://api.exemple.com/contacts?email=personne@exemple.com"devrait répondre :
HTTP/1.1 200 OK
Content-Type: application/json
{ "resultats": [], "total": 0 }Le 200 est le code du succès d'une consultation, et le standard définit son contenu pour GET comme la représentation de la ressource cible [1, §15.3.1]. Ici, la ressource cible est la recherche, qui existe et s'est exécutée correctement ; son résultat est une liste vide. C'est une lecture raisonnable de la définition, et c'est un critère de conception qui est le nôtre : l'essentiel est de décider, de documenter et d'appliquer la même règle sur toutes les routes. Si une route renvoie une liste vide et une autre un 404 pour le même type de question, le client devra écrire un cas particulier par route.
Le contraire, l'erreur déguisée en succès, est à l'origine des défaillances les plus coûteuses à détecter :
HTTP/1.1 200 OK
Content-Type: application/json
{ "erreur": "contact introuvable" }Un moniteur qui ne regarde que le code d'état marquera cette réponse comme saine. Une bibliothèque de nouvelle tentative la tiendra pour réglée. Un partenaire qui intègre votre API ne validera le corps que si on le lui a dit. Un 200 avec une erreur à l'intérieur est une défaillance entrée par la porte du bureau avec le badge du succès.
Trois cousins du 200 méritent d'être distingués quand le cas l'exige, et nous leur consacrons une seule ligne chacun : 201 quand quelque chose a été créé (normalement avec l'emplacement de la nouvelle ressource dans Location), 202 quand le travail a été accepté mais n'est pas encore terminé (si le contrat publie une consultation d'état, on la vérifie là) et 204 quand tout s'est bien passé et qu'il n'y a rien à renvoyer, par exemple lors du retrait d'un contact [1, §15.3].
Votre organisation sait-elle quelle part de ce que ses tableaux de bord appellent « succès » contient des corps avec des erreurs à l'intérieur ?
Quand le client s'est trompé : la famille 4xx
La famille 4xx dit « le problème est dans la requête ». Pour choisir à l'intérieur de cette famille, une politique de précédence peut servir, c'est-à-dire un ordre de questions qui réduit les discussions. C'est un critère qui est le nôtre, non un mandat du standard, et un contrat peut le modifier (par exemple, authentifier avant de valider est une décision de sécurité légitime) :
- La requête est-elle comprise ? (
400) - Savons-nous qui c'est ? (
401) - A-t-il l'autorisation ? (
403) - Ce qu'il demande existe-t-il ? (
404,410) - Cette méthode est-elle permise sur cette ressource ? (
405) - Arrive-t-elle dans un format que nous acceptons ? (
415) - Le contenu, une fois compris, respecte-t-il les règles métier ? (
422) - Entre-t-il en conflit avec ce qui est déjà enregistré ? (
409)
Avec cette politique, quand une requête présente deux défauts, c'est la première question de la liste qui échoue qui détermine la réponse. Cela rend les réponses prévisibles, et ce qui est prévisible peut être testé et documenté.
400 : on ne comprend pas
Le standard décrit le 400 comme la réponse lorsque le serveur ne peut pas ou ne veut pas traiter la requête à cause de ce qu'il perçoit comme une erreur du client : par exemple, une syntaxe mal formée [1, §15.5.1]. Dans le cas du contact :
curl -i "https://api.exemple.com/contacts/abc%20%20%20" # identifiant illisible
curl -i "https://api.exemple.com/contacts?email=&couleur=bleu" # paramètre inconnuSi le contrat rejette ces valeurs, les deux peuvent répondre 400. Le signal est que la requête n'a pas pu être lue comme ce qu'elle prétend être. La répéter telle quelle donnerait le même résultat, et c'est pourquoi un client bien conçu ne la réessaie pas : il la corrige ou la montre à une personne.
401 et 403 : qui êtes-vous et que pouvez-vous faire
Ce sont deux questions distinctes, et on les confond fréquemment. Le 401 dit que la requête ne comporte pas d'identifiants valides, et le standard impose d'inclure WWW-Authenticate [1, §15.5.2]. Le 403 dit que le serveur a compris la requête mais refuse de la traiter ; si des identifiants ont déjà été envoyés, le client ne devrait pas répéter automatiquement avec les mêmes [1, §15.5.4].
La différence a des conséquences en exploitation. Face à un 401, un client peut renouveler sa session et répéter. Face à un 403, répéter avec les mêmes identifiants ne change rien au résultat. Un serveur qui confond les deux place les clients dans une boucle de renouvellement d'identifiants qui ne résout rien.
Une nuance de sécurité nous importe dans une API de contacts : le standard permet à un serveur qui veut cacher l'existence d'une ressource interdite de répondre 404 au lieu de 403 [1, §15.5.4]. Si le contact d'un client n'est visible que de son propriétaire, dire à un autre client « interdit » lui confirme que cet identifiant existe. Répondre « introuvable » ne lui confirme rien. C'est une décision de politique qu'il convient d'écrire, non d'improviser route par route.
404 et 410 : ce n'est pas là
Le 404 indique qu'aucune représentation actuelle de la ressource n'a été trouvée ou que le serveur ne veut pas révéler qu'elle existe ; il ne dit pas si l'absence est temporaire ou définitive [1, §15.5.5]. Si le serveur sait qu'elle est définitive, le standard préfère le 410 [1, §15.5.11].
Pour un contact, cette différence a une valeur métier. « Cet identifiant n'a jamais existé » (404) et « il a existé puis a été fusionné avec un autre ou retiré » (410) conduisent à des actions différentes côté client : dans le premier cas, il vérifie s'il a commis une erreur de saisie ; dans le second, il cesse de le demander et cherche le contact qui l'a remplacé. Si votre exploitation ne distingue pas les deux cas, utilisez le 404, qui est le code par défaut, comme le standard lui-même le recommande lorsque le serveur ne sait pas si la condition est définitive [1, §15.5.11].
Un détail pratique qui surprend : le 404 figure parmi les codes que le standard qualifie de cacheables par heuristique [1, §15.1]. Une réponse « n'existe pas » peut rester stockée dans un cache intermédiaire, de sorte qu'un contact tout juste créé continue de ne pas apparaître pendant un moment. Si la création et la consultation s'enchaînent immédiatement, il convient de vérifier les en-têtes de cache de cette réponse.
405 et 415 : la route existe, la forme non
Un 405 dit que la méthode est connue mais que cette ressource ne l'admet pas, et il exige l'en-tête Allow avec ce qu'elle admet. Un 415 dit que le format du contenu envoyé n'est pas accepté [1, §15.5.6]. Ce sont des erreurs d'intégration, non des erreurs métier, et elles apparaissent surtout dans les premiers jours d'une nouvelle connexion.
Lorsqu'un partenaire reçoit une erreur de votre API, peut-il savoir, à la première ligne, s'il lui convient de corriger la requête, de renouveler sa session ou de cesser de demander cette ressource ?
400 contre 422 : la différence la plus débattue
La discussion la plus fréquente dans les équipes porte sur le code à utiliser quand les données sont mauvaises. La politique que nous suggérons pour ce contrat (ce n'est pas une règle universelle de HTTP), appuyée sur les définitions du standard, est courte :
| Situation | Code | Pourquoi |
|---|---|---|
| Le JSON est cassé, il manque une accolade fermante ou un paramètre n'existe pas | 400 | La requête ne peut pas être lue [1, §15.5.1] |
| Le JSON est valide et le format est accepté, mais l'adresse e-mail n'a pas d'arobase ou le téléphone ne respecte pas la règle de son pays | 422 | Le serveur comprend le contenu, la syntaxe est correcte, mais il ne peut pas traiter ce qui est demandé [1, §15.5.21] |
| Le contenu est valide, mais entre en conflit avec ce qui est déjà enregistré (un autre contact avec la même adresse e-mail) | 409 | Conflit avec l'état actuel de la ressource [1, §15.5.10] |
Le 422 se définit comme la réponse où le serveur comprend le type de contenu de la requête (c'est pourquoi le 415 ne s'applique pas) et où sa syntaxe est correcte, mais où il n'a pas pu traiter les instructions contenues [1, §15.5.21]. De nombreuses équipes utilisent le 400 pour tout, et ce n'est pas une erreur de protocole : le 400 est large. L'avantage de séparer est d'ordre opérationnel : avec le 400, le client sait que son code a produit quelque chose d'illisible (un défaut d'intégration), et avec le 422, il sait que les données n'ont pas passé une règle (un cas à montrer à un utilisateur). Deux causes différentes, deux réparations différentes, deux équipes différentes pour traiter l'alerte.
Un exemple de création avec une donnée invalide :
curl -i -X POST https://api.exemple.com/contacts \
-H "Content-Type: application/json" \
-d '{"nom": "Ana Pérez", "email": "ana-at-exemple"}'HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://api.exemple.com/problemes/email-invalide",
"title": "L'adresse e-mail n'a pas un format valide",
"status": 422,
"detail": "Le champ 'email' doit contenir une adresse e-mail.",
"instance": "/contacts"
}Le format du corps n'est pas fortuit ; il est expliqué dans la section consacrée à Problem Details.
Si vous répondez aujourd'hui 400 à tout, votre équipe de support peut-elle distinguer une intégration mal faite d'une donnée mal saisie sans ouvrir les journaux ?
409 : quand cela entre en conflit avec ce qui existe
Le 409 est réservé à la requête qui ne peut pas être menée à bien à cause d'un conflit avec l'état actuel de la ressource, dans des situations où l'appelant pourrait le résoudre et renvoyer ; le standard demande que le serveur inclue assez d'informations pour reconnaître la source du conflit [1, §15.5.10].
Pour les contacts, il apparaît de deux façons. La première est évidente : créer un contact dont l'adresse e-mail appartient déjà à un autre. La seconde est plus coûteuse et moins visible : deux canaux modifient le même contact presque en même temps. Si tous deux font un PUT avec la version complète qu'ils ont lue, celui qui arrive en second efface en silence la modification du premier. Personne n'a vu d'erreur ; le système a enregistré ce qu'on lui a demandé.
Le standard HTTP offre des outils pour cela : les requêtes conditionnelles. Un contrat peut renvoyer une étiquette de version (ETag) à chaque lecture et exiger que le client la renvoie dans If-Match lors de la modification ; si la version a déjà changé, la réponse est 412 Precondition Failed [1, §15.5.13] ; et si le client n'a même pas envoyé la condition alors que le serveur l'exige, 428 Precondition Required, conçu précisément pour éviter la « mise à jour perdue » [3, §3]. C'est le moyen de transformer une modification perdue en erreur visible plutôt qu'en trou silencieux. Le détail de ces deux codes sort du cadre de cet article ; ce qui compte ici, c'est le principe : un conflit peut être détecté, et le détecter coûte moins cher que le découvrir lors d'un audit.
Que se passe-t-il dans vos systèmes quand deux canaux modifient le même client en même temps : y a-t-il une erreur visible, ou le dernier l'emporte-t-il ?
429, 500, 502, 503 et 504 : quand le problème vient du serveur, ou de sa capacité
La famille 5xx est celle où se tromper coûte le plus cher, parce que son effet pratique est de dire aux machines « il peut être judicieux de réessayer ». Bien réessayer sauve une opération ; mal réessayer la duplique ou fait tomber le service.
429 : trop de requêtes
Bien qu'il ne figure pas dans le standard principal de HTTP mais dans une extension, le 429 est l'outil adapté pour limiter le débit. Il indique que l'appelant a envoyé trop de requêtes sur une période, et il peut inclure Retry-After pour dire combien de temps attendre ; la même spécification précise qu'elle ne définit pas comment l'utilisateur est identifié ni comment les requêtes sont comptées, et que ces réponses ne doivent pas être mises en cache [3, §4].
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/problem+json
{ "type": "https://api.exemple.com/problemes/limite-depassee",
"title": "La limite de requêtes a été dépassée",
"status": 429 }Notez que le 429 appartient à la famille des erreurs du client bien qu'il traite de capacité ; il peut refléter un quota ou une politique partagée, et pas seulement le rythme de l'appelant. Et notez Retry-After, qui est facultatif : s'il est présent, respectez-le ; sinon, suivez la politique documentée, car sans aucun signal le client devine. Dans une intégration qui consulte des contacts par lots, la différence est celle qui sépare un client qui s'adapte d'un client qui insiste chaque seconde et aggrave la saturation que l'on voulait éviter.
500 : nous ne nous attendions pas à cela
Le 500 indique une condition inattendue qui a empêché de satisfaire la requête [1, §15.6.1]. C'est le code de « une condition inattendue s'est produite de notre côté » ; le serveur peut connaître la cause, mais la réponse ne doit pas la divulguer. Deux règles pour un 500 sain. Première : le corps ne doit pas montrer la pile d'appels, la requête, le chemin des fichiers ni les clés ; le standard Problem Details met expressément en garde contre l'exposition de détails d'implémentation tels qu'un vidage de pile, car ils peuvent révéler des informations exploitables [2, §5]. Seconde : le message doit être général et porter un identifiant que votre équipe puisse retrouver dans ses journaux.
502 et 504 : un intermédiaire n'a pas obtenu de réponse
Quand votre API dépend d'un autre système (le CRM, l'ERP, le système central) et agit comme passerelle ou proxy, deux codes décrivent ce qui se passe entre les deux (dans d'autres topologies, le contrat peut choisir une autre réponse). Le 502 est la réponse lorsque le serveur a reçu une réponse invalide du système en amont ; le 504, lorsqu'il n'a pas reçu de réponse à temps [1, §15.6.3 et §15.6.5].
Un 502 ou un 504 sur une écriture est particulièrement délicat, et il en va de même d'un 500 ou d'un 503 qui arriverait après l'application de l'opération, car il ne dit pas si l'opération a été appliquée ou non. Si le client réessaie à l'aveugle la création d'un contact, il peut en créer deux. La parade se trouve dans la section sur les nouvelles tentatives.
503 : je ne peux pas maintenant, revenez dans un moment
Le 503 indique que le serveur ne peut pas traiter la requête en ce moment en raison d'une surcharge temporaire ou d'une maintenance programmée, situation qui s'atténuera probablement avec le temps ; il peut s'accompagner de Retry-After [1, §15.6.4]. Le même standard précise que son existence n'oblige pas un serveur surchargé à l'utiliser : certains refusent simplement la connexion [1, §15.6.4].
Retry-After accepte un nombre de secondes ou une date [1, §10.2.3]. Avec un 503, il indique combien de temps le service est censé rester indisponible pour le client [1, §10.2.3]. Lors d'une maintenance planifiée, cette information permet aux intégrations de vos partenaires de se mettre en pause dans l'ordre plutôt que d'échouer en cascade.
Résumé de la famille, avec la question qui fait le plus mal dans chaque cas :
| Code | Ce qu'il dit | Ce que l'appelant devrait faire | L'erreur typique |
|---|---|---|---|
429 | Vous allez trop vite | Attendre ce que dit Retry-After et revenir | Ne pas envoyer Retry-After ; le client insiste |
500 | Quelque chose d'inattendu a échoué | Réessayer avec prudence si l'opération est répétable ; alerter | Montrer la pile, la requête ou les chemins |
502 | Le système en amont a répondu quelque chose d'invalide | Vérifier si l'opération a été appliquée avant de répéter | Réessayer une création à l'aveugle |
503 | Je ne peux pas traiter maintenant | Attendre et réessayer | Renvoyer 200 avec « en maintenance » dans le corps |
504 | Le système en amont n'a pas répondu à temps | Vérifier l'état avant de répéter | Le confondre avec une défaillance du client |
Quand l'un de vos systèmes internes ralentit, vos API disent-elles à l'appelant combien de temps attendre, ou le laissent-elles décider au hasard ?
Problem Details (RFC 9457) : un corps d'erreur qu'un programme peut lire
Les codes étant choisis, il reste le corps. Sans accord, chaque API invente le sien : {"erreur": "..."}, {"message": "..."}, {"code": 7}. Le standard RFC 9457 (qui remplace le 7807) définit un format commun pour cela [2]. Identifié par le type de média application/problem+json, il définit cinq membres de base qu'il peut inclure [2, §3.1] :
| Membre | À quoi il sert |
|---|---|
type | Un identifiant du type de problème, sous forme d'URI ; les consommateurs doivent l'utiliser comme identifiant principal du problème [2, §3.1.1] |
title | Un bref résumé du type de problème, lisible par des personnes [2, §3.1.3] |
status | Le code HTTP ; il est informatif, et le générateur doit utiliser le même code dans la réponse réelle [2, §3.1.2] |
detail | Une explication de cette occurrence précise ; les consommateurs ne doivent pas l'analyser pour en extraire des informations [2, §3.1.4] |
instance | Un identifiant de cette occurrence, pour la retracer [2, §3.1.5] |
En l'absence de type, on suppose about:blank, ce qui signifie que le problème n'a pas d'autre sémantique que celle du code HTTP [2, §3.1.1 et §4.2.1]. Les clients doivent ignorer les membres d'extension qu'ils ne reconnaissent pas [2, §3.2], ce qui permet d'ajouter des champs propres (par exemple, la liste des champs invalides) sans casser personne.
Ce qui a de la valeur pour l'entreprise tient en deux règles. Première : le programme décide d'après le type, non d'après la phrase. Si la phrase de detail passe de « réessayez » à « réessayez plus tard », le comportement du client ne doit pas changer. Seconde : le status du corps et le code de la première ligne doivent coïncider ; le standard lui-même avertit que dupliquer l'information ouvre la possibilité que les deux divergent [2, §5]. Une erreur avec status: 404 dans le corps sur une réponse 200 est le cas ci-dessus, habillé d'un format standard.
Avec un type stable (et normalement un title stable), un même problème produit le même identifiant ; detail, instance et les extensions peuvent varier d'une occurrence à l'autre. Le générateur de la réponse est celui qui doit faire coïncider status avec le code ; un intermédiaire peut ensuite l'altérer. Cela permet de compter les occurrences par type dans un tableau de bord, et non par texte.
Votre organisation pourrait-elle aujourd'hui compter combien de fois un même type d'erreur s'est produit en une semaine, sans lire les messages un par un ?
Nouvelles tentatives : ce qui se répète et ce qui ne se répète pas
Choisir le code est la moitié du travail. L'autre moitié, c'est ce que le client en fait. La règle de fond vient du standard des méthodes : une requête est idempotente si la répéter produit le même effet que la faire une seule fois. PUT, DELETE et les méthodes sûres, comme GET, le sont ; POST ne l'est pas [1, §9.2.2]. Et le standard indique qu'un client ne devrait pas réessayer automatiquement une méthode non idempotente, sauf s'il a un moyen de savoir que la sémantique est idempotente dans ce cas ou de détecter que la requête d'origine n'a jamais été appliquée [1, §9.2.2].
Appliqué au contact :
| Requête | Propriété HTTP de la méthode | Conséquence d'une erreur |
|---|---|---|
GET /contacts/{id} | Sûre et idempotente | Charge supplémentaire |
PUT /contacts/{id} avec le contact complet | Idempotente | Même effet voulu |
DELETE /contacts/{id} | Idempotente | La seconde fois peut répondre 404/410, et c'est correct |
POST /contacts (création) | Non idempotente par définition | Deux contacts identiques, sauf si le contrat l'évite |
Pour sécuriser la création, il existe une pratique répandue : que le client envoie une clé d'idempotence dans un en-tête Idempotency-Key, de sorte que le serveur reconnaisse la nouvelle tentative. Une mise en garde : l'en-tête est décrit dans un brouillon de l'IETF (révision 07) qui, au 20 octobre 2026, figure comme expiré (18 avr. 2026) et n'est pas un standard publié [4] ; utilisez-le comme référence, non comme norme. Si votre contrat adopte ce schéma, documentez la conservation des clés et des réponses. Ce que propose le brouillon : la même clé avec la même requête renvoie le résultat d'origine ; la même clé avec une autre charge, 422 ; et une répétition pendant que l'originale est encore en cours, 409 [4].
La sûreté d'une nouvelle tentative dépend aussi du contrat, des effets de bord et de l'incertitude du réseau, et pas seulement de la méthode. Sur cette base, le client peut disposer d'une table de décision par code. Une version commentée pour notre cas :
| Réponse | Action du client | Pourquoi |
|---|---|---|
200, 201, 204 | Continuer | Tout s'est bien passé |
400, 422 | Ne pas réessayer ; corriger ou montrer | Répéter à l'identique donne la même chose |
401 | Renouveler les identifiants une fois ; si cela revient, s'arrêter | Une boucle de renouvellement ne résout rien |
403 | Ne pas réessayer ; escalader | Une politique le refuse |
404, 410 | Ne pas réessayer ; traiter « n'existe pas » comme un résultat | C'est une information, non une défaillance |
409, 412 | Relire l'état, décider, renvoyer | L'état a changé depuis la lecture |
429 | Attendre Retry-After et revenir | Respecte la capacité du serveur |
502, 504 en écriture | Consulter l'état avant de répéter | L'opération a pu être appliquée |
500, 503 | Seulement si la méthode est idempotente ou si le contrat offre une idempotence vérifiable : réessayer avec une attente croissante, aléatoire et plafonnée, en respectant Retry-After s'il est présent ; sinon, consulter l'état | Défaillance probablement temporaire, mais une écriture a pu être appliquée |
| Code non reconnu | Le traiter comme le x00 de sa famille [1, §15] | Règle du standard |
La ligne du 500 et du 503 appelle une nuance : l'attente croissante avec une composante aléatoire et un plafond est une pratique courante d'ingénierie, non un mandat du standard. Son but est que mille clients ne reviennent pas tous à la seconde exacte et ne fassent pas retomber un service qui était en train de se rétablir.
Si votre fournisseur répond 503 pendant quinze minutes, que font vos intégrations : attendent-elles dans l'ordre, insistent-elles sans arrêt ou s'arrêtent-elles sans prévenir ?
Ce que coûte une erreur : trois scénarios
Ce qui suit, ce sont des scénarios illustratifs, sans chiffres ; ils servent à voir le mécanisme du dommage.
1. L'intégration qui réessaie ce qu'elle ne devrait pas. Une application de vente crée des contacts dans votre API. Un jour, la base ralentit et l'API répond 504 sur certaines créations. Le code de l'application traite tout 5xx comme « réessayer », et certaines créations avaient déjà été appliquées. Résultat : des contacts en double, des campagnes qui écrivent deux fois au même client et un travail de débogage que personne n'avait prévu. Si le client, le serveur et le contrat appliquent la politique « vérifiez l'état avant de répéter » et une clé d'idempotence à la création, le même incident a beaucoup moins de chances de laisser des doublons.
2. Le tableau de bord qui reste vert. Une équipe mesure la santé de son API par le taux de réponses 5xx. L'API renvoie 200 avec une erreur dans le corps quand le CRM ne répond pas. Le tableau de bord affiche du vert alors que les canaux ne peuvent pas consulter les contacts. Dans l'autre sens, il y a aussi un coût : si chaque recherche sans résultat est renvoyée en 404, les consommateurs de l'API accumulent des « erreurs » qui n'en sont pas, et l'équipe d'astreinte apprend à ignorer l'alerte. Un tableau de bord utile exige que le code signifie quelque chose : 4xx pour ce que corrige l'appelant, 5xx pour ce que corrige l'équipe propriétaire du service.
3. L'agent qui ne sait pas quoi faire. De plus en plus d'API sont consommées par un programme qui décide seul, y compris des assistants et des agents d'intelligence artificielle qui appellent des outils. Un agent qui reçoit 200 avec la phrase « je n'ai pas pu le trouver » doit interpréter du langage naturel pour savoir s'il y a eu échec. Celui qui reçoit 404 avec un type stable sait qu'il peut signaler « n'existe pas ». Celui qui reçoit 429 avec Retry-After sait combien de temps attendre ; celui qui reçoit 503 sans aucune indication peut insister ou renoncer, selon la façon dont il a été programmé. Un detail en langage libre ne doit pas être analysé pour prendre des décisions [2, §3.1.4] ; le type, si. Un code et un type bien choisis réduisent l'ambiguïté que l'agent doit gérer. Le même sujet, vu sous l'angle de ce dont votre système a besoin pour que les agents l'utilisent, nous le traitons dans « Des API prêtes pour les agents d'IA ».
Lequel de ces trois scénarios pourrait se produire aujourd'hui dans l'une de vos intégrations sans que votre équipe le sache ?
Un parcours complet : le contact, de bout en bout
Pour clore l'exemple, une séquence avec les réponses que donnerait une API bien conçue. Chaque ligne est un appel hypothétique contre le contrat ci-dessus.
| # | Appel | Réponse | Lecture |
|---|---|---|---|
| 1 | GET /contacts?email=ana@exemple.com (existe) | 200, liste avec un contact | Succès de la consultation |
| 2 | GET /contacts?email=personne@exemple.com | 200, liste vide | La recherche a fonctionné ; il n'y a pas de correspondance |
| 3 | GET /contacts/c-0001 (existe) | 200, le contact, avec ETag | Succès avec version |
| 4 | GET /contacts/c-9999 (n'a jamais existé) | 404, Problem Details | Un résultat, non une défaillance |
| 5 | GET /contacts/c-0002 (fusionné avec un autre) | 410, Problem Details avec le remplaçant | Cesser de le demander |
| 6 | GET /contacts/%%% | 400 | Requête illisible |
| 7 | POST /contacts avec une adresse e-mail sans arobase | 422 | Donnée qui ne respecte pas la règle |
| 8 | POST /contacts avec une adresse e-mail déjà enregistrée | 409 | Entre en conflit avec l'enregistré |
| 9 | PUT /contacts/c-0001 avec un If-Match périmé | 412 | Quelqu'un a modifié avant |
| 10 | Une rafale de recherches dépassant la limite convenue | 429 avec Retry-After | Rythme dépassé |
| 11 | GET /contacts/c-0001 avec le CRM à l'arrêt | 503 avec Retry-After | Revenez dans un moment |
| 12 | POST /contacts et le CRM ne répond pas à temps | 504 | Consultez avant de répéter |
Douze lignes et dix codes distincts. Les lignes 11 et 12 relèvent d'une politique possible du contrat, pour une API qui agit comme intermédiaire. Une table de contrat documentée et vérifiée réduit l'ambiguïté entre équipes, et transforme un accord verbal en quelque chose que l'on peut vérifier.
Existe-t-il aujourd'hui, pour vos API les plus utilisées, une table de ce genre —scénario, code, lecture— ou chaque intégrateur la reconstruit-il par essais et erreurs ?
Six questions pour votre prochaine revue d'API
À emporter à la prochaine réunion d'architecture, sans avoir besoin d'ouvrir le code :
- Une erreur sort-elle avec un
200? Demandez un échantillon de réponses d'erreur et regardez la première ligne. - Le « introuvable » obéit-il à une politique unique ? La politique distingue-t-elle la recherche sans correspondance (
200), la ressource inexistante (404) et la ressource retirée (410), et s'applique-t-elle de façon cohérente à des cas équivalents sur toutes les routes ? - Distingue-t-on
400de422et de409? Si tout est400, vos équipes de support paient la différence. - Les
429et503disent-ils combien de temps attendre ?Retry-Afterest facultatif ; quand il manque, chaque client décide seul, il convient donc de documenter la politique. - Les erreurs ont-elles un format commun et un
typestable ? C'est ce qui permet à un tableau de bord, à un partenaire et à un agent de décider sans lire de prose. - Les créations peuvent-elles être répétées sans dupliquer ? Sinon, chaque
504est un risque de donnée en double.
De nombreuses décisions commencent dans le contrat ; leur mise en œuvre peut exiger des changements d'application ou de plateforme (limites de débit, persistance des clés d'idempotence, observabilité).
Avec combien de ces six questions seriez-vous aujourd'hui à l'aise face à un audit ou à un partenaire qui intègre votre API ?
Ce que fait Hábil
Hábil travaille dans l'espace entre les systèmes qui fonctionnent déjà dans votre entreprise et les nouvelles couches qui en ont besoin : canaux, partenaires, assistants et agents. Une partie de ce sujet —que chaque réponse d'une API dise clairement ce qui s'est passé et ce qu'il faut faire— est ce qui fait tenir une intégration quand quelque chose tourne mal, et cela fait partie de ce que nous proposons dans Middleware et plateformes d'API. Pour qu'un assistant ou un agent d'intelligence artificielle utilise vos systèmes avec discernement et des limites claires, voyez Intelligence artificielle d'entreprise.
La première conversation, sans frais, part de la carte de vos intégrations et des points où une réponse ambiguë pourrait vous coûter de l'argent, du temps ou des preuves. Écrivez-nous sur WhatsApp.
Consultées le 20 oct. 2026 (les textes des RFC ont été lus dans leur version officielle du RFC Editor et les passages cités ont été recoupés).
Références
- IETF, RFC 9110, HTTP Semantics (juin 2022). Sections : §9.2.2 méthodes idempotentes ; §10.2.3
Retry-After; §15 et §15.1 classes de codes, formules recommandées, code non reconnu traité commex00, codes cacheables par heuristique ; §15.3.1200; §15.5.1400; §15.5.2401; §15.5.4403; §15.5.5404; §15.5.6405; §15.5.10409; §15.5.11410; §15.5.13412; §15.5.21422; §15.6.1500; §15.6.3502; §15.6.4503; §15.6.5504. https://www.rfc-editor.org/rfc/rfc9110.html - IETF, RFC 9457, Problem Details for HTTP APIs (remplace le RFC 7807). §3 et §3.1 membres et type de média
application/problem+json; §3.1.1type; §3.1.2status; §3.1.4detail; §3.2 extensions ; §4.2.1about:blank; §5 considérations de sécurité. https://www.rfc-editor.org/rfc/rfc9457.html - IETF, RFC 6585, Additional HTTP Status Codes, §3 (
428 Precondition Required) et §4 (429 Too Many Requests,Retry-After, sans mise en cache). https://www.rfc-editor.org/rfc/rfc6585.html - IETF HTTPAPI, The Idempotency-Key HTTP Header Field (Internet-Draft
draft-ietf-httpapi-idempotency-key-header, révision 07 ; brouillon, non standard publié ; expiration enregistrée le 18 avr. 2026 dans le Datatracker). https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/
- Des API prêtes pour les agents d'IA : ce dont votre système a besoin pour qu'un agent l'utilise sous contrôle →
- REST ou gRPC : quand choisir l'un ou l'autre, et pourquoi votre système central ne parle ni l'un ni l'autre →
- Middleware et plateformes d'API →
Votre activité fait face à ces défis ?
Vous préférez l'e-mail ? Écrivez-nous à hola@habil.mx