Intégration et API22 min

Codes HTTP dans une API métier : que répondre quand on cherche un contact, et ce que coûte une erreur

Par · 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.

Codes HTTP dans une API métier : que répondre quand on cherche un contact, et ce que coûte une erreur
Si la réponse est…La famille est…Règle générale
Un vrai succès2xxPoursuivre
Une requête à corriger4xxCorriger, s'identifier ou attendre selon le code et le contrat
Une défaillance du serveur ou d'une dépendance5xxNe 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] :

Les trois parties d'une réponse, et pourquoi le numéro commande
FamilleSignification dans le standardCe que cela dit à l'appelant
2xxLa requête a été reçue, comprise et acceptéePoursuivez
3xxUne autre action est nécessaire pour la mener à bienAllez ailleurs ou réutilisez ce que vous avez
4xxLa requête contient une erreur ou ne peut pas être satisfaiteExaminez le code précis et le contrat ; la répéter à l'identique n'aide presque jamais
5xxLe serveur a échoué à traiter une requête en apparence valideExaminez 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ère

Elles 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) :

  1. La requête est-elle comprise ? (400)
  2. Savons-nous qui c'est ? (401)
  3. A-t-il l'autorisation ? (403)
  4. Ce qu'il demande existe-t-il ? (404, 410)
  5. Cette méthode est-elle permise sur cette ressource ? (405)
  6. Arrive-t-elle dans un format que nous acceptons ? (415)
  7. Le contenu, une fois compris, respecte-t-il les règles métier ? (422)
  8. 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 inconnu

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

400 contre 422 : la différence la plus débattue
SituationCodePourquoi
Le JSON est cassé, il manque une accolade fermante ou un paramètre n'existe pas400La 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 pays422Le 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)409Conflit 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 :

503 : je ne peux pas maintenant, revenez dans un moment
CodeCe qu'il ditCe que l'appelant devrait faireL'erreur typique
429Vous allez trop viteAttendre ce que dit Retry-After et revenirNe pas envoyer Retry-After ; le client insiste
500Quelque chose d'inattendu a échouéRéessayer avec prudence si l'opération est répétable ; alerterMontrer la pile, la requête ou les chemins
502Le système en amont a répondu quelque chose d'invalideVérifier si l'opération a été appliquée avant de répéterRéessayer une création à l'aveugle
503Je ne peux pas traiter maintenantAttendre et réessayerRenvoyer 200 avec « en maintenance » dans le corps
504Le système en amont n'a pas répondu à tempsVérifier l'état avant de répéterLe 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] :

Problem Details (RFC 9457) : un corps d'erreur qu'un programme peut lire
MembreÀ quoi il sert
typeUn 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]
titleUn bref résumé du type de problème, lisible par des personnes [2, §3.1.3]
statusLe 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]
detailUne explication de cette occurrence précise ; les consommateurs ne doivent pas l'analyser pour en extraire des informations [2, §3.1.4]
instanceUn 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 :

Nouvelles tentatives : ce qui se répète et ce qui ne se répète pas
RequêtePropriété HTTP de la méthodeConséquence d'une erreur
GET /contacts/{id}Sûre et idempotenteCharge supplémentaire
PUT /contacts/{id} avec le contact completIdempotenteMême effet voulu
DELETE /contacts/{id}IdempotenteLa seconde fois peut répondre 404/410, et c'est correct
POST /contacts (création)Non idempotente par définitionDeux 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 :

Nouvelles tentatives : ce qui se répète et ce qui ne se répète pas
RéponseAction du clientPourquoi
200, 201, 204ContinuerTout s'est bien passé
400, 422Ne pas réessayer ; corriger ou montrerRépéter à l'identique donne la même chose
401Renouveler les identifiants une fois ; si cela revient, s'arrêterUne boucle de renouvellement ne résout rien
403Ne pas réessayer ; escaladerUne politique le refuse
404, 410Ne pas réessayer ; traiter « n'existe pas » comme un résultatC'est une information, non une défaillance
409, 412Relire l'état, décider, renvoyerL'état a changé depuis la lecture
429Attendre Retry-After et revenirRespecte la capacité du serveur
502, 504 en écritureConsulter l'état avant de répéterL'opération a pu être appliquée
500, 503Seulement 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'étatDéfaillance probablement temporaire, mais une écriture a pu être appliquée
Code non reconnuLe 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.

Un parcours complet : le contact, de bout en bout
#AppelRéponseLecture
1GET /contacts?email=ana@exemple.com (existe)200, liste avec un contactSuccès de la consultation
2GET /contacts?email=personne@exemple.com200, liste videLa recherche a fonctionné ; il n'y a pas de correspondance
3GET /contacts/c-0001 (existe)200, le contact, avec ETagSuccès avec version
4GET /contacts/c-9999 (n'a jamais existé)404, Problem DetailsUn résultat, non une défaillance
5GET /contacts/c-0002 (fusionné avec un autre)410, Problem Details avec le remplaçantCesser de le demander
6GET /contacts/%%%400Requête illisible
7POST /contacts avec une adresse e-mail sans arobase422Donnée qui ne respecte pas la règle
8POST /contacts avec une adresse e-mail déjà enregistrée409Entre en conflit avec l'enregistré
9PUT /contacts/c-0001 avec un If-Match périmé412Quelqu'un a modifié avant
10Une rafale de recherches dépassant la limite convenue429 avec Retry-AfterRythme dépassé
11GET /contacts/c-0001 avec le CRM à l'arrêt503 avec Retry-AfterRevenez dans un moment
12POST /contacts et le CRM ne répond pas à temps504Consultez 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 :

  1. Une erreur sort-elle avec un 200 ? Demandez un échantillon de réponses d'erreur et regardez la première ligne.
  2. 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 ?
  3. Distingue-t-on 400 de 422 et de 409 ? Si tout est 400, vos équipes de support paient la différence.
  4. Les 429 et 503 disent-ils combien de temps attendre ? Retry-After est facultatif ; quand il manque, chaque client décide seul, il convient donc de documenter la politique.
  5. Les erreurs ont-elles un format commun et un type stable ? C'est ce qui permet à un tableau de bord, à un partenaire et à un agent de décider sans lire de prose.
  6. Les créations peuvent-elles être répétées sans dupliquer ? Sinon, chaque 504 est 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

  1. 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é comme x00, codes cacheables par heuristique ; §15.3.1 200 ; §15.5.1 400 ; §15.5.2 401 ; §15.5.4 403 ; §15.5.5 404 ; §15.5.6 405 ; §15.5.10 409 ; §15.5.11 410 ; §15.5.13 412 ; §15.5.21 422 ; §15.6.1 500 ; §15.6.3 502 ; §15.6.4 503 ; §15.6.5 504. https://www.rfc-editor.org/rfc/rfc9110.html
  2. 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.1 type ; §3.1.2 status ; §3.1.4 detail ; §3.2 extensions ; §4.2.1 about:blank ; §5 considérations de sécurité. https://www.rfc-editor.org/rfc/rfc9457.html
  3. 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
  4. 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/