Développement web et mobile

Codes de statut HTTP : référence complète, signification et bon usage

Débutant17 min de lectureMis à jour

Cette page a été traduite de l'anglais avec l'aide de l'IA, puis vérifiée par rapport à l'original. Lire la version anglaise

Le code de statut est la première chose qu'un client apprend d'une réponse, et souvent la seule qu'un intermédiaire lit : en choisir un mauvais casse sans bruit le cache, les retries et la gestion d'erreurs. Cette référence explique le modèle des cinq classes, recense dans des tableaux chaque code enregistré avec son sens et son usage, puis passe en revue les paires que l'on confond, les règles de redirection et de cache, et le choix des codes lors de la conception d'une API.

Ce que vous allez apprendre

  • Expliquer la structure d'une ligne de statut et ce que signale chacune des cinq classes
  • Retrouver n'importe quel code de statut et savoir quand c'est la bonne réponse
  • Trancher correctement entre les paires que l'on confond sans cesse, comme 301 et 308 ou 401 et 403
  • Appliquer la sémantique des redirections sans changer la méthode d'une requête par accident
  • Prévoir quelles réponses peuvent être mises en cache et comment les requêtes conditionnelles produisent un 304
  • Choisir des codes de statut adaptés en concevant une API HTTP et signaler les erreurs de façon cohérente

Toute réponse HTTP commence par un nombre à trois chiffres, et ce nombre est la seule partie de la réponse que beaucoup de systèmes lisent. Le navigateur s'en sert pour décider s'il suit une redirection. Le cache, pour décider s'il a le droit de stocker le corps. Le load balancer, pour juger si le backend est en bonne santé. La bibliothèque cliente, pour choisir entre lever une exception et réessayer. Un mauvais code ne produit en général pas d'erreur visible : il en produit une subtile, des mois plus tard, quand un proxy met en cache ce qu'il n'aurait pas dû ou qu'une boucle de retry martèle un endpoint qui n'avait aucune chance de répondre.

La spécification de référence est la RFC 9110, qui a remplacé l'ancienne famille RFC 7231 et constitue désormais le document unique décrivant la sémantique HTTP pour les versions 1.1, 2 et 3. La liste officielle des codes attribués est l'IANA HTTP Status Code Registry. Cette page est un compagnon pratique des deux : ce que signifie chaque code, quand c'est la bonne réponse, et où les développeurs se trompent immanquablement.

La ligne de statut et les cinq classes

En HTTP/1.1, une réponse commence par la version du protocole, le code de statut et une phrase de raison (reason phrase) :

HTTP/1.1 404 Not Found
Content-Type: application/problem+json
Content-Length: 91

{"type":"https://example.com/errors/no-such-order","title":"Order not found","status":404}

La phrase de raison n'a qu'une valeur indicative. Elle existe pour les humains qui lisent un flux brut, les clients ne doivent pas s'y fier, et HTTP/2 et HTTP/3 l'ont purement supprimée : ils ne transportent que le code numérique, sous forme de pseudo-en-tête. Ne l'analysez jamais.

Le premier chiffre est la partie dont le sens est défini, et il fait office de contrat : un client qui reçoit un code qu'il ne connaît pas doit le traiter comme le x00 générique de sa classe. Une réponse 499 est traitée comme un 400, un 572 comme un 500. C'est cette règle qui permet d'enregistrer de nouveaux codes sans casser les clients existants, et c'est pourquoi choisir un numéro non enregistré d'apparence plausible fait moins de dégâts que choisir un code enregistré au sens erroné — mais reste pire que choisir le bon.

ClasseNomSignification
1xxInformationalLa requête a été reçue ; le traitement continue. Réponse intermédiaire.
2xxSuccessfulLa requête a été reçue, comprise et acceptée.
3xxRedirectionUne action supplémentaire est nécessaire pour mener la requête à bien.
4xxClient errorLa requête est fautive et échouera de nouveau si elle n'est pas modifiée.
5xxServer errorLa requête est peut-être valide ; le serveur n'a pas su y répondre.

La frontière entre 4xx et 5xx a un vrai poids opérationnel. C'est elle qui sépare « votre faute » de « notre faute » sur un tableau de bord, elle que surveillent les health checks, elle qui sert de base au calcul des error budgets. Renvoyer 500 pour une requête mal formée pollue vos propres alertes ; renvoyer 400 pour une panne interne masque un véritable incident.

1xx — Informational

Des réponses intermédiaires, envoyées avant la réponse finale. Un client qui ne comprend pas un 1xx doit l'ignorer et continuer d'attendre.

CodeNomQuand il apparaît
100ContinueLe client a envoyé Expect: 100-continue avec les seuls en-têtes, pour demander s'il doit transmettre un corps volumineux. Le serveur répond 100 pour l'y inviter, ou un 4xx pour refuser avant le début de l'envoi.
101Switching ProtocolsLe serveur accepte de changer de protocole, comme lors d'un handshake WebSocket après Upgrade: websocket.
102ProcessingRéponse d'attente WebDAV pour les opérations longues. Dépréciée ; utilisez 103 ou gardez la connexion ouverte.
103Early HintsEnvoyé avant la réponse finale pour transmettre des en-têtes Link, afin que le navigateur précharge des ressources ou ouvre des connexions pendant que le serveur travaille encore.

Early Hints est le seul des quatre qui mérite d'être ajouté délibérément à une application moderne : sur une page rendue côté serveur et lente, il permet au navigateur de commencer à récupérer feuilles de style et polices avant même que le HTML existe.

2xx — Successful

CodeNomQuand l'utiliser
200OKLe succès par défaut. Le corps contient le résultat de la requête.
201CreatedUne nouvelle ressource a été créée. Ajoutez un en-tête Location qui pointe vers elle, et en général sa représentation dans le corps.
202AcceptedLa requête a été acceptée pour un traitement asynchrone et le résultat n'est pas encore connu. Fournissez un moyen de suivre l'avancement.
203Non-Authoritative InformationUn proxy de transformation a modifié le contenu. Rare en dehors des intermédiaires.
204No ContentSuccès sans rien à renvoyer : un DELETE, ou un PUT dont le client connaît déjà l'état résultant. Il ne doit y avoir aucun corps.
205Reset ContentSuccès ; le client doit vider le formulaire à l'origine de la requête. Rarement utilisé.
206Partial ContentUne requête Range réussie. C'est la base des téléchargements reprenables et de la navigation dans les médias.
207Multi-StatusWebDAV : un corps XML contenant plusieurs codes de statut indépendants.
208Already ReportedWebDAV : les membres d'une collection ont déjà été énumérés.
226IM UsedLa réponse résulte de l'application d'instance manipulations, comme un encodage delta.

La paire qui compte au quotidien, c'est 201 contre 200. Si la requête a créé quelque chose d'adressable, dites-le et donnez son adresse : clients et caches peuvent alors travailler avec la nouvelle ressource sans deviner son URL. Et 204 signifie strictement aucun corps : y joindre du JSON viole la spécification, et certains clients réagissent en levant une exception.

3xx — Redirection

CodeNomMéthode conservée ?Quand l'utiliser
300Multiple Choicess.o.Plusieurs représentations existent et le client doit choisir. Presque jamais utilisé.
301Moved PermanentlyNon, en pratiqueLa ressource a une nouvelle URL permanente. Transforme historiquement un POST en GET.
302FoundNon, en pratiqueDéplacement temporaire. Transforme lui aussi un POST en GET dans tous les clients réels.
303See OtherNon, par conception« Récupérez le résultat à cette autre URL avec GET. » La bonne réponse à la soumission réussie d'un formulaire.
304Not Modifieds.o.La copie en cache est toujours valide ; il n'y a pas de corps. Réponse à une requête conditionnelle.
305Use Proxys.o.Déprécié pour des raisons de sécurité. À ne pas utiliser.
306(inutilisé)s.o.Réservé ; s'appelait « Switch Proxy ».
307Temporary RedirectOuiDéplacement temporaire, méthode et corps conservés.
308Permanent RedirectOuiDéplacement permanent, méthode et corps conservés.

Bien faire ses redirections

Ce désordre historique mérite d'être compris, car il façonne encore les comportements. La RFC 2616 disait que 301 et 302 ne devaient pas changer la méthode de la requête sans confirmation de l'utilisateur. Les navigateurs ont ignoré cette règle et transformé en GET tout POST redirigé, si bien que la spécification a fini par céder et documenter le comportement réel. Il n'existait alors plus aucun moyen de dire « déplacé, et gardez la méthode » : d'où l'ajout de 307 et 308.

Les règles pratiques :

  • 301 ou 308. Les deux sont permanents, et les deux sont mis en cache par les clients — souvent sans limite de durée —, donc aucun ne doit être utilisé à la légère. Choisissez 301 pour les déménagements de site ordinaires, les redirections vers l'hôte et le schéma canoniques et les pages qui changent d'adresse : il est universellement pris en charge et les moteurs de recherche ont bâti des décennies de comportement autour de lui. Choisissez 308 quand l'endpoint accepte POST, PUT ou PATCH et que la méthode doit survivre, par exemple lors du changement de chemin d'une API versionnée.
  • 302 ou 307. Les deux sont temporaires. 302 convertira un POST en GET ; 307, non. Si vous redirigez la soumission d'un formulaire et voulez que le navigateur refasse le POST vers la nouvelle adresse, il vous faut 307. Si vous voulez le comportement sûr, 303 l'énonce explicitement au lieu de s'appuyer sur une règle de fait.
  • 303 après une écriture. Le pattern Post/Redirect/Get existe pour empêcher qu'un rafraîchissement du navigateur ne soumette un paiement une seconde fois. Répondez au POST réussi par un 303 avec un Location qui pointe vers la page de confirmation.

Deux mises en garde opérationnelles. Il est difficile de revenir sur une redirection permanente : un navigateur qui a mis un 301 en cache peut ne plus reposer la question pendant très longtemps, alors testez d'abord avec un code temporaire et passez au permanent une fois sûr de vous. Et chaque redirection coûte un aller-retour : les chaînes de trois ou quatre sauts, fréquentes après plusieurs migrations de site, sont un problème de performance mesurable et doivent être ramenées à un seul saut. Quand vous déboguez une redirection qui abîme les paramètres de requête, l'encodeur/décodeur d'URL rend la différence immédiatement visible.

4xx — Client error

CodeNomQuand l'utiliser
400Bad RequestLa requête est mal formée : syntaxe invalide, corps impossible à analyser, framing incorrect. Ce n'est pas une erreur fourre-tout.
401UnauthorizedAuthentification absente ou invalide. Doit inclure WWW-Authenticate.
402Payment RequiredRéservé ; certaines API l'utilisent pour des dépassements de quota ou des échecs de facturation.
403ForbiddenLe serveur a compris l'identité et refuse. Se réauthentifier n'y changera rien.
404Not FoundAucune ressource à cette URL, ou le serveur refuse de dire s'il en existe une.
405Method Not AllowedL'URL existe, mais pas pour cette méthode. Doit inclure un en-tête Allow.
406Not AcceptableAucune représentation ne correspond aux en-têtes Accept.
407Proxy Authentication RequiredComme 401, mais pour un proxy ; utilise Proxy-Authenticate.
408Request TimeoutLe client a mis trop de temps à envoyer la requête. Peut être réessayé sans risque.
409ConflictLa requête entre en conflit avec l'état actuel : clé en double, modification concurrente, transition d'état invalide.
410GoneSupprimée délibérément et définitivement. Plus fort et plus utile que 404 quand vous le savez.
411Length RequiredLa requête doit comporter un Content-Length.
412Precondition FailedUne précondition If-Match ou If-Unmodified-Since n'est pas satisfaite. C'est le fondement du verrouillage optimiste.
413Content Too LargeLe corps dépasse la limite du serveur. Anciennement « Payload Too Large ».
414URI Too LongLa cible de la requête dépasse la limite du serveur.
415Unsupported Media TypeLe Content-Type ne fait pas partie de ceux que l'endpoint accepte.
416Range Not SatisfiableLa plage d'octets demandée est hors de la ressource.
417Expectation FailedL'en-tête Expect ne peut pas être satisfait.
418I'm a teapotUn poisson d'avril de 1998, réservé par l'IANA pour qu'il ne soit jamais attribué.
421Misdirected RequestLa connexion a abouti à un serveur incapable de produire une réponse pour cette autorité. Se rencontre avec le connection coalescing de HTTP/2.
422Unprocessable ContentSyntaxe valide, sens incorrect. Le choix habituel pour les échecs de validation des règles métier.
423LockedWebDAV : la ressource est verrouillée.
424Failed DependencyWebDAV : une requête dont celle-ci dépend a échoué.
425Too EarlyLe serveur refuse de traiter une requête early data susceptible d'être rejouée.
426Upgrade RequiredLe client doit changer de protocole, par exemple passer à TLS.
428Precondition RequiredLe serveur exige une requête conditionnelle, pour éviter les mises à jour perdues.
429Too Many RequestsRate limiting. Ajoutez Retry-After.
431Request Header Fields Too LargeLes en-têtes dépassent la limite — en général à cause d'un cookie trop gros.
451Unavailable For Legal ReasonsBloqué par une injonction légale. Le numéro est un clin d'œil à Fahrenheit 451.

5xx — Server error

CodeNomQuand l'utiliser
500Internal Server ErrorUn échec non géré. Le fourre-tout, jamais la réponse délibérée à une situation connue.
501Not ImplementedLe serveur ne prend pas du tout en charge la méthode.
502Bad GatewayUne passerelle a reçu une réponse invalide d'un serveur en amont.
503Service UnavailableMomentanément incapable de traiter la requête — surcharge ou maintenance. Ajoutez Retry-After.
504Gateway TimeoutUne passerelle n'a pas reçu à temps de réponse d'un serveur en amont.
505HTTP Version Not SupportedLa version du protocole utilisée par la requête n'est pas prise en charge.
506Variant Also NegotiatesUne erreur de configuration de la négociation de contenu.
507Insufficient StorageWebDAV : pas assez d'espace pour mener la requête à bien.
508Loop DetectedWebDAV : boucle infinie pendant le traitement.
510Not ExtendedObsolète ; le mécanisme d'extension auquel il appartenait n'a jamais été adopté.
511Network Authentication RequiredUn portail captif qui exige une connexion. Doit être généré par le proxy qui intercepte, jamais par un serveur d'origine.

Les paires que l'on confond sans cesse

401 ou 403. 401 dit « je ne sais pas qui vous êtes, ou vos identifiants ont été refusés » et oblige le serveur à envoyer WWW-Authenticate. 403 dit « je sais qui vous êtes, et c'est toujours non ». La conséquence concrète porte sur le comportement du client : un 401 doit déclencher une invite de connexion ou un rafraîchissement du token ; un 403, non. Beaucoup d'API renvoient 403 pour un token expiré, ce qui enferme les clients dans un échec permanent au lieu de provoquer un rafraîchissement.

Il existe une troisième option à connaître. Quand l'existence même d'une ressource est confidentielle, renvoyer 404 à un appelant non autorisé est une mesure de durcissement légitime : cela évite de confirmer qu'un objet portant cet identifiant existe.

400 ou 422. 400 porte sur la forme, 422 sur le sens. Un corps qui n'est pas du JSON valide, c'est un 400 ; un corps JSON valide dont la endDate précède la startDate, c'est un 422. Certaines API bien connues utilisent 400 pour tout et restent parfaitement utilisables : la propriété de loin la plus importante est donc la cohérence, avec un corps qui nomme les champs fautifs. La RFC 9457 définit précisément pour cela un media type standard de problem details, qu'il vaut la peine d'adopter pour que les clients analysent les erreurs de façon uniforme.

404 ou 410. 404 signifie « rien ici, et je ne dis pas s'il y a jamais eu quelque chose ». 410 signifie « ceci a existé et a été supprimé délibérément ». Si vous savez que la ressource a disparu pour de bon, 410 est plus informatif : les robots d'indexation abandonnent l'URL plus vite et les clients peuvent cesser de réessayer.

409, 422 ou 412. 409 est un conflit avec l'état actuel de la ressource : créer un utilisateur dont l'e-mail existe déjà, ou faire changer d'état une commande déjà expédiée. 422 est un problème qui tient au seul contenu soumis. 412 est la réponse à l'échec d'une précondition sur une requête conditionnelle, et c'est ainsi que l'on implémente le contrôle de concurrence optimiste avec ETag et If-Match.

502, 503 ou 504. Les trois proviennent en général d'un proxy plutôt que de votre application, et savoir les distinguer est le moyen le plus rapide de localiser un incident. 502 signifie que l'amont a répondu quelque chose d'incompréhensible — souvent un processus planté ou une incompatibilité de protocole. 503 signifie que le serveur tourne et refuse délibérément pour l'instant : surchargé, en maintenance, ou sans aucun backend sain dans le pool. 504 signifie que l'amont n'a jamais répondu à temps, ce qui désigne une dépendance lente, un deadlock ou un timeout réglé plus bas dans le proxy que dans l'application. Quand une passerelle se trouve devant vos services, sa propre configuration de timeout et de retry détermine lequel des trois vos utilisateurs voient — voir gestion d'API et passerelles.

200 avec une erreur à l'intérieur. L'anti-pattern le plus nuisible de la liste. Chaque intermédiaire, chaque système de supervision et chaque client générique le traite comme un succès : les échecs disparaissent des tableaux de bord et la logique de retry ne se déclenche jamais. Mettez le résultat dans le code de statut et le détail dans le corps.

Cache et requêtes conditionnelles

La RFC 9111 définit quelles réponses peuvent être stockées, et le code de statut en est le point de départ. Sont mis en cache par défaut — autrement dit, un cache partagé peut les stocker même sans Cache-Control explicite — les codes 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414 et 501. Tous les autres exigent une autorisation explicite par des directives de cache. Cette liste réserve deux surprises à bien retenir : 404 et 301 sont mis en cache par défaut, si bien qu'un déploiement momentanément cassé qui renvoie des 404, ou une redirection publiée par erreur, peut rester en mémoire des caches et des navigateurs bien après la correction de l'origine.

En pratique, ne comptez pas sur les valeurs par défaut. Le Cache-Control de la réponse tranche la question : no-store interdit tout stockage, no-cache autorise le stockage mais impose une revalidation avant réutilisation, private le réserve au navigateur, max-age fixe la durée de fraîcheur en secondes, et must-revalidate interdit de servir une copie périmée après expiration.

Le 304 vient des requêtes conditionnelles. Le serveur envoie un validateur — un ETag ou un Last-Modified — et le client le renvoie à la requête suivante :

GET /api/orders/42 HTTP/1.1
Host: example.com
If-None-Match: "a3f1c0"

HTTP/1.1 304 Not Modified
ETag: "a3f1c0"
Cache-Control: max-age=60

Un 304 n'a pas de corps, et c'est tout l'intérêt : le client détient déjà le contenu et avait seulement besoin de savoir qu'il est toujours valide. Les mêmes validateurs pilotent If-Match pour les écritures, transformant une situation de mise à jour perdue en un 412 propre.

Choisir ses codes en concevant une API

Un petit ensemble choisi délibérément vaut mieux qu'une couverture exhaustive. La plupart des API bien conçues utilisent une douzaine de codes environ : 200, 201, 202, 204, 301 ou 308 pour les endpoints déplacés, 400, 401, 403, 404, 409, 422, 429, et 500 avec 503 pour le mode dégradé. Consignez-les dans le contrat de l'API pour que tous les endpoints se comportent de la même façon et que les clients puissent gérer les erreurs de manière générique.

Au-delà du choix lui-même, quelques habitudes portent leurs fruits. Renvoyez un corps d'erreur lisible par une machine, dans une forme unique et documentée — les problem details de la RFC 9457, avec type, title, status, detail et les éventuelles erreurs par champ —, pour qu'un client puisse afficher un message sans traiter chaque endpoint comme un cas particulier. Ne laissez pas fuiter de stack traces ni d'identifiants internes dans ce corps : journalisez-les avec un identifiant de corrélation et renvoyez cet identifiant à la place. Envoyez Retry-After avec 429 et 503 pour que les clients temporisent intelligemment au lieu de deviner, et vérifiez que votre propre code client le respecte. Ajoutez Allow avec 405 et WWW-Authenticate avec 401, deux en-têtes que la spécification impose. Privilégiez les opérations idempotentes partout où c'est possible, car c'est l'idempotence qui rend sûr le retry d'un 500 ou d'un 504. Et gardez à l'esprit que fetch dans le navigateur se résout quel que soit le statut, si bien que le code client doit vérifier le statut explicitement — un point traité dans les fondamentaux de JavaScript, tandis que le HttpClient d'Angular adopte la convention inverse et lève une erreur.

Enfin, traitez les codes de statut comme un instrument d'exploitation, pas seulement comme un contrat avec les clients. Tracez séparément les taux de 4xx et de 5xx, alertez sur les 5xx, et surveillez les codes précis qui trahissent un problème structurel : une hausse des 401 après un déploiement indique en général un problème de token ou d'horloge, une hausse des 504 le ralentissement d'une dépendance, et une hausse des 429 soit un client abusif, soit une limite trop serrée. Les considérations de conception plus larges derrière ces choix sont traitées dans la conception d'API REST, et le côté framework dans construire des API REST avec Spring Boot.

À retenir

  • Le premier chiffre fait contrat : un code inconnu est traité comme le x00 générique de sa classe, et la séparation 4xx/5xx décide, sur vos tableaux de bord, à qui revient un incident.
  • Utilisez 307 et 308 quand une redirection doit conserver la méthode et le corps ; 301 et 302 transforment en pratique un POST en GET, et 303 le dit explicitement après une écriture.
  • 401 veut dire « authentifiez-vous », 403 veut dire « non » ; 400 signale une requête mal formée, 422 une requête sémantiquement invalide ; 502, 503 et 504 distinguent un amont défaillant, un serveur qui refuse et un amont trop lent.
  • Un 200 avec une erreur dans le corps met en échec caches, retries et supervision : mettez le résultat dans la ligne de statut et le détail dans un corps problem details RFC 9457.
  • 200, 301, 404 et plusieurs autres sont mis en cache par défaut : une redirection erronée ou un 404 passager peut survivre au déploiement qui l'a causé, sauf si Cache-Control en décide autrement.
  • Choisissez une douzaine de codes, documentez-les dans le contrat de l'API, envoyez toujours Retry-After, Allow et WWW-Authenticate là où c'est requis, et vérifiez explicitement le code dans le code client.

Sources et lectures complémentaires

Questions fréquentes

Quelle est la différence entre 401 et 403 ?
401 Unauthorized signifie que la requête ne comportait pas d'authentification valide, ou que les identifiants fournis ont été refusés ; la réponse doit inclure un en-tête WWW-Authenticate qui indique au client comment s'authentifier, et une nouvelle tentative avec de meilleurs identifiants peut réussir. 403 Forbidden signifie que le serveur sait qui vous êtes et refuse malgré tout. Répéter un 403 avec la même identité ne fonctionnera jamais : un client ne doit donc pas redemander une connexion.
Un échec de validation doit-il renvoyer 400 ou 422 ?
Utilisez 400 Bad Request quand la requête elle-même est mal formée : JSON cassé, en-tête obligatoire absent, paramètre impossible à analyser. Utilisez 422 Unprocessable Content quand la syntaxe est correcte mais que le contenu est sémantiquement faux, par exemple une date de fin antérieure à la date de début ou une devise que le compte ne peut pas utiliser. Les deux sont légitimes ; l'essentiel est que votre API soit cohérente et renvoie un corps lisible par une machine qui indique quel champ a échoué.
Peut-on renvoyer un 200 avec une erreur dans le corps ?
C'est techniquement permis et presque toujours une erreur. Les caches, les proxys, les politiques de retry, les devtools du navigateur, les tableaux de bord de supervision et les bibliothèques clientes se fondent tous sur le code de statut : un échec déguisé en 200 devient invisible pour toutes les couches, sauf le code qui analyse votre enveloppe spécifique. Faites porter le résultat par le code de statut et le détail par le corps, idéalement au format problem details de la RFC 9457.
Quels codes de statut un client doit-il réessayer ?
Réessayez sur 408, 429, 502, 503 et 504, car ils signalent une condition transitoire, et respectez tout en-tête Retry-After avant d'appliquer un backoff exponentiel avec jitter. Ne réessayez pas les autres codes 4xx : ils décrivent un problème dans la requête qui sera toujours là la fois suivante. Réessayer un 500 n'est sûr que si l'opération est idempotente, puisque le serveur a pu l'appliquer partiellement.