Lesson 4/8 10 min

Choosing the right status code

201 with its Location, 204 with no body, 400 vs 422, 409 for conflicts. And the forbidden anti-pattern: a 200 hiding an error in the body.

Le code, c'est déjà une phrase

Rappel express du cours HTTP : les status codes vivent en familles. 2xx ça a marché, 3xx va voir ailleurs, 4xx c'est ta faute (client), 5xx c'est la mienne (serveur). Tu connais déjà les huit codes de base. Ici, on monte d'un cran.

Côté API, chaque réponse est une phrase complète, et le code EST le sens. Avant même de lire le JSON, ton client lit le code et sait quoi faire. Un livre ajouté au catalogue de la bibliothèque ne répond pas comme un livre déjà emprunté, qui ne répond pas comme un ISBN mal formaté. Trois situations, trois codes différents, trois phrases nettes.

Le designer d'API ne se contente pas de « ça marche / ça marche pas ». Il choisit le code juste pour chaque situation métier. C'est tout l'objet de cette leçon : apprendre à parler status couramment.

Les codes du succès qui en disent plus que 200

Tout 2xx n'est pas un 200 OK. Deux codes plus précis racontent mieux ce qui vient de se passer.

201 Created : j'ai créé, et voilà où

Quand un POST /api/livres réussit et crée une ressource, la bonne réponse n'est pas 200, c'est 201 Created. Et elle s'accompagne d'un header capital : Location, qui dit où vit la nouvelle ressource.

HTTP/1.1 201 Created
Location: /api/livres/87

Le client n'a pas à deviner l'identifiant du livre qu'il vient de créer : l'API le lui tend sur un plateau. Il peut enchaîner direct sur GET /api/livres/87. Un simple 200 sans Location, c'est dire « c'est bon » sans dire « où » : le client devra fouiller.

204 No Content : succès, rien à ajouter

204 No Content, c'est le succès muet. L'opération a réussi et il n'y a strictement rien à renvoyer dans le body. Les deux cas typiques :

  • un DELETE /api/livres/87 réussi : le livre est parti, que veux-tu renvoyer de plus ?
  • un PUT qui met à jour sans avoir besoin de retourner la ressource.

Le body est vide, point. Renvoyer 200 avec {} ou un message « supprimé avec succès » est moins propre : 204 dit déjà tout, dans le code lui-même.

400 contre 422 : la distinction qui sépare les pros

Voici LE point de la leçon. Côté 4xx, deux codes se ressemblent en apparence et ne disent pas du tout la même chose. Bien les distinguer, c'est rendre ton API diagnosticable.

  • 400 Bad Request : la syntaxe est cassée. Le serveur n'arrive même pas à lire la requête. JSON malformé, accolade manquante, virgule en trop. Le message est illisible, on s'arrête à la porte.
  • 422 Unprocessable Content : la syntaxe est OK, le serveur a parfaitement lu le JSON, mais le contenu est invalide côté métier. Le titre est manquant, l'ISBN est au mauvais format, la date est dans le futur. La phrase est bien construite, mais elle dit une bêtise.

L'image simple : 400, c'est une phrase grammaticalement fausse, on n'arrive pas à la lire. 422, c'est une phrase parfaitement écrite mais fausse sur le fond. Le serveur la comprend, et la refuse.

422 est un code sûr pour toute API. Il s'appelle désormais « Unprocessable Content » (et non plus « Unprocessable Entity »). Surtout, depuis la RFC 9110 (2022), il fait partie du cœur de HTTP : il n'est plus réservé à WebDAV comme à l'époque. Tu peux l'utiliser partout, sans arrière-pensée.

409 pour le conflit, 401/403 pour l'accès

409 Conflict : l'état refuse

Parfois la requête est syntaxiquement parfaite ET valide sur le fond, mais l'état actuel des données la rend impossible. C'est 409 Conflict.

  • tu veux emprunter le livre 42, mais il est déjà emprunté par quelqu'un d'autre ;
  • tu crées un livre avec un ISBN qui existe déjà dans le catalogue.

Rien à reprocher à ta requête en elle-même : c'est le monde qui dit non. Ce n'est ni un 400 (la syntaxe va), ni un 422 (le contenu est valide) : c'est l'état qui bloque.

401 contre 403 : pas connecté contre pas le droit

Deux codes d'accès qu'on confond tout le temps :

  • 401 Unauthorized : tu n'es pas authentifié. Pas de token, ou token invalide. L'API ne sait même pas qui tu es.
  • 403 Forbidden : l'API sait très bien qui tu es (token valide), mais tu n'as pas le droit. Un lecteur lambda qui tente de supprimer un livre du catalogue : il est bien connecté, mais ce n'est pas pour lui.

On creusera les tokens en leçon 7. Retiens la phrase : 401 = « je ne sais pas qui tu es », 403 = « je sais qui tu es, et c'est non ».

L'erreur qui rend une API aveugle

Le piège mortel : répondre 200 OK avec une erreur cachée dans le body. Du genre :

HTTP/1.1 200 OK
Content-Type: application/json

{"error": "livre introuvable"}

C'est grave, et pas qu'esthétiquement. Les clients testent le code HTTP, pas le body : ils croient que tout va bien et continuent sur des données vides. Les caches se fient au code : un 200 peut être mis en cache, donc ton erreur aussi. Le monitoring compte les 4xx et 5xx pour t'alerter : noyée dans des 200, ta panne devient invisible. Le code doit dire la vérité : ressource absente, c'est 404, pas 200.

Prédis avant de lire

Un client envoie POST /api/emprunts pour emprunter le livre 42. Le JSON est parfait, le livre existe... mais il est déjà emprunté par quelqu'un d'autre. Ton API répond 400, 409 ou 422 ?

Voir la réponse

409 Conflict. Ce n'est pas 400 : la syntaxe est parfaite. Ce n'est pas 422 : le contenu est valide, le livre 42 existe et la demande a du sens. C'est l'état actuel qui refuse, le livre est déjà sorti. Quand la requête est correcte mais que le monde dit non, c'est 409.

L'arbre de décision du status code

Devant chaque réponse, pose-toi les questions dans l'ordre : ça a marché ? Si non, à qui la faute ? Et de quel genre ? Suis l'arbre, tu tombes sur le bon code.

Arbre de décision vertical du status code. En haut : « Ça a marché ? ». Branche oui : « Ressource créée ? » → 201 (avec Location), sinon « Rien à renvoyer ? » → 204, sinon 200. Branche non : « Faute du client ? ». Si oui : syntaxe cassée → 400, sinon validation métier → 422, sinon état qui refuse → 409, sinon problème d'accès → 401 ou 403, sinon ressource absente → 404. Si la faute vient du serveur → 500. Ça a marché ? oui non 2xx · Succès Ressource créée ? oui 201 + Location non Rien à renvoyer ? oui 204 non 200 4xx / 5xx · Échec Faute du client ? non 500 serveur oui Syntaxe cassée ? 400 non Validation métier ? 422 non État qui refuse ? 409 non Problème d'accès ? 401/403 sinon 404 introuvable
Une seule règle : pose les questions dans l'ordre, et le code juste tombe tout seul.

À toi : fais parler ton API en status

Un terminal simulé branché sur l'API de la bibliothèque. Tu vas créer un livre, casser le JSON exprès, envoyer un contenu invalide, puis supprimer. À chaque coup, lis le code avant le body : il te dit déjà tout.

🖥️ Terminal simulé · les status codes de l'API biblio
$

The code is already a sentence

Quick recap from the HTTP course: status codes come in families. 2xx it worked, 3xx look elsewhere, 4xx it's your fault (client), 5xx it's mine (server). You already know the eight basic codes. Here, we go up a notch.

On the API side, every response is a full sentence, and the code IS the meaning. Before even reading the JSON, your client reads the code and knows what to do. A book added to the library catalogue doesn't answer like a book already on loan, which doesn't answer like a badly formatted ISBN. Three situations, three different codes, three crisp sentences.

The API designer doesn't settle for "it worked / it didn't". They pick the right code for each business situation. That's the whole point of this lesson: learning to speak status fluently.

The success codes that say more than 200

Not every 2xx is a 200 OK. Two more precise codes tell a better story about what just happened.

201 Created: I created it, and here's where

When a POST /api/books succeeds and creates a resource, the right answer isn't 200, it's 201 Created. And it comes with a crucial header: Location, which says where the new resource lives.

HTTP/1.1 201 Created
Location: /api/books/87

The client doesn't have to guess the id of the book it just created: the API hands it over on a plate. It can chain straight into GET /api/books/87. A plain 200 with no Location says "all good" without saying "where": the client has to go digging.

204 No Content: success, nothing to add

204 No Content is the silent success. The operation worked and there's strictly nothing to return in the body. The two typical cases:

  • a successful DELETE /api/books/87: the book is gone, what more would you return?
  • a PUT that updates without needing to return the resource.

The body is empty, full stop. Returning 200 with {} or a "deleted successfully" message is less clean: 204 already says it all, in the code itself.

400 vs 422: the distinction that sets pros apart

Here's THE point of the lesson. On the 4xx side, two codes look alike and mean very different things. Telling them apart makes your API diagnosable.

  • 400 Bad Request: the syntax is broken. The server can't even read the request. Malformed JSON, missing brace, extra comma. The message is unreadable, we stop at the door.
  • 422 Unprocessable Content: the syntax is fine, the server read the JSON perfectly, but the content is invalid on the business side. The title is missing, the ISBN is in the wrong format, the date is in the future. The sentence is well built, but it says something silly.

The simple image: 400 is a grammatically wrong sentence, we can't read it. 422 is a perfectly written sentence that's wrong on the substance. The server understands it, and refuses it.

422 is a safe code for any API. It's now called "Unprocessable Content" (no longer "Unprocessable Entity"). Above all, since RFC 9110 (2022), it's part of core HTTP: it's no longer reserved for WebDAV like it used to be. You can use it anywhere, with no second thoughts.

409 for conflict, 401/403 for access

409 Conflict: the state refuses

Sometimes the request is syntactically perfect AND valid on the substance, but the current state of the data makes it impossible. That's 409 Conflict.

  • you want to borrow book 42, but it's already on loan to someone else;
  • you create a book with an ISBN that already exists in the catalogue.

Nothing wrong with your request itself: it's the world that says no. It's not a 400 (the syntax is fine), nor a 422 (the content is valid): it's the state that blocks.

401 vs 403: not logged in vs not allowed

Two access codes people mix up all the time:

  • 401 Unauthorized: you're not authenticated. No token, or an invalid token. The API doesn't even know who you are.
  • 403 Forbidden: the API knows perfectly well who you are (valid token), but you don't have the right. A regular reader trying to delete a book from the catalogue: they're logged in, but it's not for them.

We'll dig into tokens in lesson 7. Remember the line: 401 = "I don't know who you are", 403 = "I know who you are, and it's no".

The mistake that blinds an API

The deadly trap: answering 200 OK with an error hidden in the body. Something like:

HTTP/1.1 200 OK
Content-Type: application/json

{"error": "book not found"}

It's serious, and not just aesthetically. Clients test the HTTP code, not the body: they think all is well and carry on with empty data. Caches trust the code: a 200 can be cached, so your error gets cached too. Monitoring counts 4xx and 5xx to alert you: drowned in 200s, your outage goes invisible. The code must tell the truth: missing resource means 404, not 200.

Predict before reading on

A client sends POST /api/loans to borrow book 42. The JSON is perfect, the book exists... but it's already on loan to someone else. Does your API answer 400, 409 or 422?

Show the answer

409 Conflict. It's not 400: the syntax is perfect. It's not 422: the content is valid, book 42 exists and the request makes sense. It's the current state that refuses, the book is already out. When the request is correct but the world says no, it's 409.

The status code decision tree

For every response, ask the questions in order: did it work? If not, whose fault is it? And what kind? Follow the tree, you land on the right code.

Vertical decision tree for the status code. Top: "Did it work?". Yes branch: "Resource created?" → 201 (with Location), else "Nothing to return?" → 204, else 200. No branch: "Client's fault?". If yes: broken syntax → 400, else business validation → 422, else state refuses → 409, else access problem → 401 or 403, else missing resource → 404. If the fault is the server's → 500. Did it work? yes no 2xx — Success Resource created? yes 201 + Location no Nothing to return? yes 204 no 200 4xx / 5xx — Failure Client's fault? no 500 server yes Broken syntax? 400 no Business validation? 422 no State refuses? 409 no Access problem? 401/403 else 404 not found
One single rule: ask the questions in order, and the right code falls out by itself.

Your turn: make your API speak status

A simulated terminal wired to the library API. You'll create a book, break the JSON on purpose, send invalid content, then delete. Each time, read the code before the body: it already tells you everything.

🖥️ Simulated terminal · the library API status codes
$

🎯 Pratique

S'entraîner (clique pour ouvrir) :

💬 Ré-explique sans regarder
Ré-explique sans regarder

Explique à un collègue la différence entre 400, 422 et 409, avec un exemple métier pour chacun.

Une bonne explication dit : 400 = la syntaxe est cassée, le serveur ne peut pas lire la requête (JSON malformé). 422 = syntaxe OK, mais le contenu est invalide métier (titre manquant, ISBN au mauvais format). 409 = requête correcte et valide, mais l'état la refuse (livre déjà emprunté, ISBN déjà pris). En somme : on monte de « illisible » à « lisible mais faux » à « correct mais impossible vu l'état ».
🧠 Rappel libre
Rappel libre

Sans remonter : après un POST qui crée une ressource, quel code renvoies-tu, et quel header est obligatoire ? Et pour un DELETE réussi ?

Après une création réussie : 201 Created, avec le header Location qui pointe vers la nouvelle ressource (ex. Location: /api/livres/87). Le client sait ainsi où la trouver. Pour un DELETE réussi sans rien à renvoyer : 204 No Content, body vide. Dans les deux cas, un simple 200 serait moins précis.
⚖️ Juge le code de l'IA
Accepter ou rejeter le code de l'IA

Tu demandes à l'IA de gérer le cas « livre introuvable » dans ton API. Elle répond : « Réglé ! Je renvoie toujours 200 OK avec {"success": false} dans le body, ça simplifie le front : il n'a plus qu'un seul cas à gérer. » Tu acceptes, ou tu rejettes ?

À rejeter, c'est l'anti-pattern central de la leçon. Un 200 qui cache une erreur dans le body rend l'API aveugle : les clients testent le code (ils croiront que tout va bien), les caches peuvent stocker un 200 (donc l'erreur aussi), et le monitoring ne voit plus les pannes noyées dans des 200. « Simplifier le front » ne justifie pas de mentir sur le code. Le bon réflexe : ressource absente → 404. Le code doit dire la vérité.
Un POST tente de créer un compte avec un email déjà pris. Le JSON est valide, l'email est bien formé. Quel code ?
Le client envoie un JSON parfaitement formé, mais le champ titre est manquant (obligatoire). Quel code ?
Le body envoyé est {"titre":} : le JSON est cassé, le serveur n'arrive pas à le lire. Quel code, et lequel pour « pas connecté du tout » ?
Un lecteur connecté avec un token valide envoie POST /api/emprunts pour le livre 42. Le JSON est parfait, le livre existe, mais il est déjà emprunté. Quel code, et lequel s'il n'avait simplement pas le droit d'emprunter ?
Next step

The codes speak. What's left is the content: the JSON, and the two headers that avoid misunderstandings. Lesson 5: the data contract.

Lesson 5: JSON: the data contract →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement