Votre API renvoie 400 Bad Request avec le corps {"error": "invalid input"}. Un développeur humain ouvre la documentation, vérifie la charge utile, repère le champ manquant et le corrige en une minute. Un agent lit les mêmes deux mots, n'a rien sur quoi agir et fait la seule chose qu'il peut : renvoie la même requête. Puis encore. Puis il abandonne et dit à l'utilisateur que l'API est cassée.
Les réponses d'erreur sont la partie d'une API dont les agents dépendent le plus et que les équipes conçoivent en dernier. Une bonne erreur indique à l'appelant ce qui n'a pas fonctionné, si une nouvelle tentative pourrait aider, et ce qu'il faut changer. Un agent peut agir sur les trois. Une erreur vague transforme un problème récupérable en une tâche échouée.
Ce guide est écrit du point de vue de l'API. Notre article sur la récupération d'erreurs d'agent couvre ce que le client doit faire avec les nouvelles tentatives, le backoff et les coupe-circuits. Celui-ci couvre ce que votre API doit retourner pour que cette logique client fonctionne.
Apidog est important ici car les réponses d'erreur sont la partie la moins testée de la plupart des API. Vous pouvez les définir dans la spécification, les simuler et les vérifier au même endroit où vous testez le chemin de succès.
Les trois questions auxquelles une erreur doit répondre
Chaque réponse d'erreur qu'un agent reçoit devrait lui permettre de répondre à trois choses sans deviner.
Est-ce ma faute ou la vôtre ? Un 4xx signifie que la requête était erronée et que la répéter inchangée échouera à nouveau. Un 5xx signifie que quelque chose a mal tourné sur le serveur et que la même requête pourrait réussir plus tard. Les agents qui ne peuvent pas faire la distinction entre les deux réessayent soit indéfiniment sur une erreur de validation, soit abandonnent sur un problème transitoire.
Dois-je réessayer, et quand ? Certaines erreurs 4xx sont récupérables et d'autres non. 429 est récupérable après un délai. 409 peut être récupérable après relecture de l'état. 422 n'est pas récupérable sans modifier la charge utile. Dites explicitement ce qu'il en est.
Que dois-je changer exactement ? C'est le champ que la plupart des API omettent. "La validation a échoué" est inutile. "Le champ customer.postal_code est requis lorsque country est US" est une correction que l'agent peut appliquer lors de la prochaine tentative.
Intégrez ces trois éléments dans chaque erreur et la plupart des tempêtes de réessais des agents disparaîtront.
Utiliser un format d'erreur structuré
N'inventez pas une forme. La RFC 9457, Problem Details for HTTP APIs, en définit une et elle est bien supportée :
{
"type": "https://api.example.com/errors/validation-failed",
"title": "Validation failed",
"status": 422,
"detail": "The field 'customer.postal_code' is required when 'country' is 'US'.",
"instance": "/v1/orders",
"errors": [
{
"field": "customer.postal_code",
"code": "required_conditional",
"message": "Required when country is US. Provide a 5-digit or 9-digit US postal code.",
"example": "94107"
}
],
"retryable": false,
"next_action": "Add customer.postal_code to the request body and send again."
}
Quatre parties portent le poids pour un agent.
detail est une phrase complète nommant le champ réel et la règle réelle. Pas une catégorie. La chose spécifique qui a échoué sur cette requête.
Le tableau errors est lisible par machine, une entrée par problème, avec un chemin de champ qu'un agent peut mapper à la charge utile qu'il a envoyée. Renvoie toutes les défaillances en une seule fois. Les renvoyer une par une transforme une seule correction en cinq allers-retours.
retryable est un booléen, pas quelque chose à déduire d'un code de statut. C'est l'extension qui aide le plus les agents, et elle ne coûte qu'un seul champ.
next_action est un texte d'instruction clair. Les modèles suivent les instructions explicites dans un corps de réponse plus fiablement qu'ils ne raisonnent à partir des codes d'erreur, et une phrase ici transforme souvent une tâche échouée en une tâche accomplie.
Le guide de conception des erreurs d'API de Google parvient à des conclusions similaires d'une direction différente, notamment que les détails d'erreur appartiennent à une liste structurée plutôt qu'à un texte en prose.
Dites quand revenir
Pour tout ce qui est transitoire, indiquez quand. Un agent qui sait attendre 30 secondes attend 30 secondes. Un agent qui ne sait pas choisira quelque chose, et ce quelque chose est généralement trop court.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/problem+json
{
"type": "https://api.example.com/errors/rate-limited",
"title": "Rate limit exceeded",
"status": 429,
"detail": "You have used 1000 of 1000 requests in the current minute window.",
"retryable": true,
"retry_after_seconds": 30,
"next_action": "Wait 30 seconds before sending this request again. Do not retry sooner."
}
L'en-tête Retry-After accepte soit un délai en secondes, soit une date HTTP ; les secondes sont plus faciles à interpréter pour un client. Envoyez-le comme en-tête pour les clients standard et répétez-le dans le corps pour le modèle. La duplication est peu coûteuse et les deux consommateurs obtiennent ce qu'ils lisent le mieux. Les spécificités de la limite de débit sont couvertes dans notre guide sur le dépassement de la limite de débit et dans comment implémenter la limitation de débit d'API si vous êtes du côté serveur.
Le même modèle s'applique au 503 pendant la maintenance et au 409 sur une ressource verrouillée. Toute erreur où l'attente est la bonne réponse devrait comporter un nombre.
Ne jamais divulguer d'informations internes, ne jamais retourner rien
Deux modes de défaillance se situent aux extrêmes opposés, et tous deux nuisent aux agents.
Le premier est la trace de pile. Le retour de texte d'exception interne expose les versions du framework, les chemins de fichiers et parfois des fragments de requête. C'est un problème de sécurité avant d'être un problème d'agent, et les préoccupations de notre article sur le test d'API contre les entrées non fiables s'appliquent directement. Cela inonde également la fenêtre de contexte avec du texte sur lequel le modèle ne peut pas agir.
Le second est l'erreur vide : un 500 sans corps, ou {"error": true}. L'agent n'apprend rien, et ses seules options sont de réessayer ou d'abandonner.
La voie médiane est une erreur publique stable avec un ID de corrélation :
{
"type": "https://api.example.com/errors/internal",
"title": "Internal error",
"status": 500,
"detail": "The order could not be created due to an internal error. No order was created.",
"retryable": true,
"retry_after_seconds": 5,
"request_id": "req_01J8ZK3M2Q",
"next_action": "Retry once after 5 seconds. If it fails again, stop and report request_id req_01J8ZK3M2Q."
}
La phrase « Aucune commande n'a été créée » est la partie la plus précieuse. Les agents confrontés à une écriture ambiguë doivent décider si une nouvelle tentative risque un doublon, et la plupart décident mal. Dites-leur dans quel état vous êtes. Si vous ne pouvez pas le promettre, rendez l'opération idempotente et indiquez-le, ce qui est le modèle de notre article sur les clés d'idempotence pour les agents IA.
L'request_id vous ramène au fil de vos journaux lorsqu'un humain lit finalement la transcription. Associez-le aux pratiques de notre guide sur l'observabilité des API afin que l'ID se résolve réellement en quelque chose.
Les erreurs appartiennent à la spécification
Si une forme d'erreur ne figure pas dans votre document OpenAPI, elle n'existe pas pour les clients générés, les maquettes et les outils d'agent. La plupart des spécifications décrivent un 200 en détail, puis balayent tout le reste du revers de la main.
responses:
'201':
description: Order created
content:
application/json:
schema: { $ref: '#/components/schemas/Order' }
'422':
description: >
Validation failed. Not retryable without changing the request body.
The errors array names each invalid field.
content:
application/problem+json:
schema: { $ref: '#/components/schemas/Problem' }
'429':
description: >
Rate limited. Retryable. Wait for retry_after_seconds before sending again.
content:
application/problem+json:
schema: { $ref: '#/components/schemas/Problem' }
Ces descriptions ne sont pas de la décoration. Lorsque vous générez des outils d'agent à partir de la spécification, comme dans notre guide sur la transformation d'une spécification OpenAPI en outils d'agent, ce texte devient ce que le modèle lit concernant le cas d'échec. Une description qui dit "réessayable, attendre d'abord" produit un meilleur comportement qu'une description qui dit "Trop de requêtes".
Testez les erreurs, pas seulement les succès
Les chemins d'erreur sont là où la couverture de test s'effondre, car leur déclenchement demande des efforts. La simulation (mocking) élimine cet effort.

Définissez chaque réponse d'erreur dans votre projet d'API, puis simulez-les afin que l'agent puisse gérer chaque cas à la demande. Dans Apidog, vous pouvez ajouter les réponses d'échec à la définition du point de terminaison et basculer une simulation entre elles, ce qui vous donne un moyen reproductible d'exécuter l'agent contre un 422, un 429 et un 500 sans rien casser de réel. Notre article sur l'exécution d'agents contre des simulations au lieu de la production couvre l'habitude plus large.
Cinq cas à construire :
- Échec de validation avec plusieurs champs incorrects à la fois. Assurez-vous que chaque problème est renvoyé en une seule réponse, et que la prochaine tentative de l'agent corrige tous les problèmes plutôt qu'un seul.
- Limite de débit avec une attente. Assurez-vous que l'agent attend au moins
retry_after_secondsau lieu de marteler. - Erreur serveur lors d'une écriture. Assurez-vous que l'agent ne crée pas silencieusement un doublon lorsqu'il réessaie.
- Échec d'authentification. Assurez-vous que l'agent s'arrête plutôt que de réessayer, car aucun délai ne résoudra un jeton invalide. Notre article sur les clés API à privilège minimum pour les agents IA couvre l'aspect des informations d'identification.
- Corps d'erreur malformé. Retournez quelque chose qui n'est pas un JSON valide et confirmez que l'agent se dégrade gracieusement. Les proxys en amont vous feront cela à un moment donné.
Enregistrez l'ensemble en tant que scénarios pour qu'ils s'exécutent en CI. La gestion des erreurs régresse silencieusement, généralement lorsque quelqu'un refactorise un sérialiseur, et la suite de tests du chemin nominal ne le remarquera pas.
Ce que valent de meilleures erreurs
La valeur apparaît à trois endroits, et elle est facile à mesurer une fois que vous y regardez.
Moins de tentatives gâchées. Un agent confronté à {"error": "invalid input"} réessaie généralement la charge utile identique deux ou trois fois avant d'abandonner. Chaque tentative coûte un tour de modèle et la conversation complète comme contexte. Une réponse nommant le champ manquant produit généralement une tentative corrigée. C'est la différence entre quatre appels et deux sur une erreur de validation courante.
Moins d'escalades. Les agents qui ne peuvent pas récupérer confient la tâche à un humain. Chaque transfert évitable est le résultat coûteux que l'agent était censé éviter. Les erreurs qui nomment une correction maintiennent l'exécution dans l'automatisation.
Débogage plus court. Lorsqu'une personne est nécessaire, un request_id accompagné d'un detail précis transforme une recherche dans les journaux en une seule consultation. C'est le même argument que notre guide sur l'observabilité des API avance à propos de la corrélation, appliqué au moment où une exécution échoue.
Il y a un quatrième avantage, facile à manquer : les mêmes améliorations aident les développeurs humains. Personne ne s'est jamais plaint qu'un message d'erreur était trop spécifique sur le champ erroné.
Concevez également pour l'escalade
Certaines erreurs ne sont réellement pas récupérables par l'agent. Une portée manquante, un compte fermé, une règle qui nécessite une décision humaine. Pour celles-ci, le rôle de l'erreur est de transférer proprement : dire ce qui s'est passé, dire ce qu'une personne doit faire, et porter l'ID de corrélation qui rend le transfert peu coûteux.
Cette réponse doit atterrir là où un humain la lira. Si l'agent est un environnement d'exécution de code travaillant sur des tâches assignées, la plateforme environnante est généralement l'endroit où elle atterrit. Sharkly conserve le résultat de l'agent et la trace d'exécution sur la Tâche et achemine les éléments nécessitant une réponse ou une révision vers une Boîte de réception, de sorte qu'une exécution bloquée est visible comme du travail plutôt que comme une ligne dans un journal. Votre texte d'erreur est ce qui rend ce transfert utile, car un message indiquant « entrée invalide » ne donne au relecteur pas plus qu'il n'en a donné à l'agent.

Ne faites pas analyser de la prose à l'agent
Un dernier anti-modèle, courant dans les API qui ont évolué organiquement. Le code de statut est correct, le corps est une phrase, et chaque échec distinct a une formulation différente :
{ "message": "Sorry, that didn't work. Please check your details and try again." }
Un agent ne peut y répondre qu'en devinant. Pire encore, les équipes l'associent souvent à un statut 200, de sorte que la bibliothèque cliente ne voit même pas d'échec.
Deux règles règlent le problème. Donnez à chaque échec distinct un code lisible par machine stable, afin que l'agent puisse se brancher sur insufficient_funds plutôt que sur l'expression "pas assez". Et ne retournez jamais un échec avec un code de statut de succès, quel que soit l'argument de commodité côté client. Un 200 avec une erreur à l'intérieur est invisible pour toutes les politiques de réessai, tous les tableaux de bord et toutes les alertes que vous possédez.
Une liste de contrôle pour les erreurs lisibles par les agents
- Chaque erreur utilise un format structuré cohérent dans toute l'API.
detailnomme le champ ou la condition spécifique, jamais une catégorie.- Les erreurs de validation renvoient chaque problème en une seule fois, avec les chemins des champs.
- Un booléen
retryableapparaît sur chaque erreur. - Les erreurs récupérables comportent un délai en secondes, dans l'en-tête et le corps.
- Les échecs d'écriture indiquent si quelque chose a été créé ou modifié.
- Chaque erreur comporte un ID de corrélation qui se résout dans vos journaux.
- Pas de traces de pile, pas de chaînes de framework, pas de SQL.
- Les réponses d'erreur sont documentées dans la spécification avec des descriptions lisibles par les agents.
- Des mocks existent pour chaque erreur, et des tests enregistrés les exécutent en CI.
Les erreurs sont une interface. Concevez-les pour l'appelant que vous avez réellement, qui est de plus en plus un modèle qui fera exactement ce que votre corps de réponse lui dit de faire. Téléchargez Apidog pour définir les formes d'erreur et les simuler avant que l'agent ne les rencontre réellement.
Questions fréquemment posées
Dois-je utiliser RFC 9457 ou mon propre format d'erreur ? Utilisez RFC 9457 à moins que vous n'ayez déjà un format cohérent en production. La cohérence l'emporte sur la standardisation : basculer la moitié de vos points de terminaison vers une nouvelle forme est pire que de conserver une seule forme partout. Ajoutez les extensions retryable et next_action à celle que vous utilisez.
Le texte next_action peut-il être inclus en toute sécurité dans une réponse d'API ? Oui, lorsque votre service le génère à partir d'un ensemble fixe de modèles. Ne reproduisez jamais le contenu fourni par l'utilisateur dans ce champ, car un agent le lit comme une instruction et cela constitue un chemin d'injection d'invite. Notre article sur le test des API contre les entrées non fiables couvre ce risque.
Les erreurs de validation doivent-elles être 400 ou 422 ? Utilisez 400 lorsque la requête est mal formée, comme un JSON cassé, et 422 lorsque la requête est parsée mais échoue aux règles métier. Les agents bénéficient de cette séparation car les correctifs sont différents. Si vous utilisez déjà l'un pour les deux, documentez-le plutôt que de le modifier.
Combien de détails est-ce trop ? Arrêtez-vous au point où l'appelant en a assez pour agir. Le nom du champ, la règle et une valeur d'exemple sont généralement suffisants. Les identifiants internes, le texte de la requête et les cadres de pile dépassent la limite.
Les messages d'erreur sont-ils comptabilisés dans la fenêtre de contexte ? Oui, et une erreur verbeuse répétée lors de nouvelles tentatives s'accumule rapidement. Gardez-les en dessous de quelques centaines de jetons. Notre article sur la réduction des réponses d'API pour les agents s'applique aux échecs autant qu'aux succès.
Comment empêcher un agent de réessayer une erreur non récupérable ? Définissez retryable: false, indiquez-le dans next_action, et appliquez-le dans l'enveloppe de l'outil afin que le jugement du modèle ne soit pas la seule garantie. La prudence est de mise ici.
