Intermédiaire

Statuts HTTP

Les codes de statut HTTP indiquent le résultat d'une requête. Bien les utiliser est essentiel pour concevoir une API compréhensible et facile à déboguer.

Vue d'ensemble des familles de statuts

FamilleSignificationExemple
1xxInformatif (lié aux WebSockets)101 Switching Protocols
2xxRequête acceptée et terminée200 OK, 201 Created
3xxRedirection301 Moved Permanently
4xxErreur côté client400 Bad Request, 404 Not Found
5xxErreur côté serveur500 Internal Server Error

Requête acceptée (2xx)

Ces codes indiquent que la requête a été correctement traitée.

200 - OK

Code générique de succès. Utilisable avec GET, PUT, PATCH et DELETE. La réponse contient un body.

python
from http import HTTPStatus

@books_bp.route('/<int:book_id>', methods=['GET'])
def get_book(book_id: int):
    book = book_service.get_by_id(book_id)
    if not book:
        return jsonify({"error": "Livre non trouvé"}), HTTPStatus.NOT_FOUND
    return jsonify(book), HTTPStatus.OK  # 200

201 - Created

À utiliser avec POST lors de la création d'une ressource. Acceptable dans certains cas de PUT (si le PUT accepte la création à la volée).

python
@books_bp.route('', methods=['POST'])
def create_book():
    data = request.get_json()
    dto = CreateBookDTO(**data)
    new_book = book_service.create(dto)
    return jsonify(new_book), HTTPStatus.CREATED  # 201

204 - No Content

À utiliser lorsque la requête réussit mais qu'il n'y a aucun contenu à renvoyer. Fréquent avec DELETE ou certains PUT/PATCH.

python
@books_bp.route('/<int:book_id>', methods=['DELETE'])
def delete_book(book_id: int):
    book_service.delete(book_id)
    return '', HTTPStatus.NO_CONTENT  # 204

HTTPStatus en Python

Plutôt que de coder en dur les valeurs numériques (200, 404, etc.), utilisez le module http.HTTPStatus de Python. Le code est plus lisible et moins sujet aux erreurs :

python
from http import HTTPStatus

HTTPStatus.OK              # 200
HTTPStatus.CREATED         # 201
HTTPStatus.NO_CONTENT      # 204
HTTPStatus.BAD_REQUEST     # 400
HTTPStatus.NOT_FOUND       # 404

Quel code de statut est le plus approprié pour la réponse d'un POST qui crée une ressource ?

Erreurs client (4xx)

Ces codes indiquent que la requête envoyée par le client est incorrecte ou non autorisée.

400 - Bad Request

La requête est mal formée : le body, les params ou la query ne passent pas la validation.

python
from pydantic import ValidationError

@books_bp.route('', methods=['POST'])
def create_book():
    data = request.get_json()
    try:
        dto = CreateBookDTO(**data)
    except ValidationError as e:
        return jsonify({"error": "Données invalides", "details": e.errors()}), HTTPStatus.BAD_REQUEST
    new_book = book_service.create(dto)
    return jsonify(new_book), HTTPStatus.CREATED

401 - Unauthorized

Le client doit s'authentifier pour accéder à la ressource. Également utilisable pour un échec de login.

python
@books_bp.route('', methods=['POST'])
def create_book():
    token = request.headers.get('Authorization')
    if not token:
        return jsonify({"error": "Authentification requise"}), HTTPStatus.UNAUTHORIZED
    # ...

403 - Forbidden

Le client est authentifié mais n'a pas les droits pour accéder à la ressource.

python
@admin_bp.route('/users', methods=['GET'])
def list_users():
    current_user = get_current_user(request)
    if current_user.role != 'admin':
        return jsonify({"error": "Accès interdit"}), HTTPStatus.FORBIDDEN
    # ...

401 vs 403

La distinction est importante :

  • 401 : "Qui êtes-vous ?" → le client n'est pas identifié
  • 403 : "Je sais qui vous êtes, mais vous n'avez pas le droit" → le client est identifié mais pas autorisé

404 - Not Found

La ressource demandée n'existe pas.

python
@books_bp.route('/<int:book_id>', methods=['GET'])
def get_book(book_id: int):
    book = book_service.get_by_id(book_id)
    if not book:
        return jsonify({"error": "Livre non trouvé"}), HTTPStatus.NOT_FOUND
    return jsonify(book), HTTPStatus.OK

405 - Method Not Allowed

L'endpoint existe, mais la méthode HTTP utilisée n'est pas supportée.

Par exemple, tenter un DELETE sur un endpoint qui n'accepte que GET et POST.

429 - Too Many Requests

Le client a envoyé trop de requêtes dans un intervalle de temps donné. C'est le mécanisme de rate limiting.

python
# Avec flask-limiter
from flask_limiter import Limiter

limiter = Limiter(app, default_limits=["100 per minute"])

@books_bp.route('', methods=['GET'])
@limiter.limit("50 per minute")
def get_books():
    # Maximum 50 requêtes par minute par client
    return jsonify(books), HTTPStatus.OK

Un utilisateur connecté tente d'accéder à une page d'administration sans être administrateur. Quel code de statut renvoyer ?

Erreurs serveur (5xx)

Ces codes indiquent un problème côté serveur qui empêche le traitement de la requête.

500 - Internal Server Error

Code générique : le code backend a planté. C'est souvent une exception non gérée.

python
@books_bp.route('/<int:book_id>', methods=['GET'])
def get_book(book_id: int):
    try:
        book = book_service.get_by_id(book_id)
        return jsonify(book), HTTPStatus.OK
    except Exception as e:
        # Logger l'erreur pour le débogage
        app.logger.error(f"Erreur interne: {e}")
        return jsonify({"error": "Erreur interne du serveur"}), HTTPStatus.INTERNAL_SERVER_ERROR

Ne jamais exposer les détails d'une 500

En production, ne renvoyez jamais le détail de l'erreur au client (stack trace, message d'exception). Cela expose des informations sensibles sur votre code. Loggez l'erreur côté serveur et renvoyez un message générique au client.

503 - Service Unavailable

Le serveur ne répond pas ou est temporairement indisponible (maintenance, surcharge).

504 - Gateway Timeout

Le serveur a mis trop de temps à répondre. Fréquent avec les providers cloud lors d'un déploiement raté ou d'une surcharge.

Récapitulatif des statuts les plus utilisés

CodeNomUsage
200OKSuccès générique (GET, PUT, PATCH)
201CreatedRessource créée (POST)
204No ContentSuccès sans body (DELETE)
400Bad RequestRequête mal formée
401UnauthorizedAuthentification requise
403ForbiddenDroits insuffisants
404Not FoundRessource introuvable
405Method Not AllowedMauvaise méthode HTTP
429Too Many RequestsRate limit dépassé
500Internal Server ErrorErreur côté serveur

À retenir

Points clés

  • Les codes 2xx indiquent un succès : utilisez 201 pour POST et 204 pour DELETE sans body
  • Les codes 4xx signalent une erreur du client : distinguez bien 401 (non authentifié) de 403 (non autorisé)
  • Les codes 5xx indiquent un problème serveur : ne jamais exposer les détails en production
  • Utilisez http.HTTPStatus en Python pour un code lisible et maintenable
  • Chaque endpoint doit renvoyer le code de statut le plus spécifique possible