← HUB ✦ Holberton School — CS / Web ✦

RESTFUL
API

HTTP, méthodes, status codes, Flask et sécurité — le protocole du web maîtrisé.

QU'EST-CE QU'UNE API REST ?
🌐 DÉFINITION

Une API REST (Representational State Transfer) est une interface qui permet à deux systèmes de communiquer via HTTP. Le client envoie une requête, le serveur renvoie une réponse — généralement en JSON.

REST n'est pas un protocole mais un style d'architecture basé sur 6 contraintes : stateless, client-serveur, cacheable, interface uniforme, système en couches, code on demand (optionnel).

🕹️ ANALOGIE GAMING

Imagine un jeu MMO. Ton personnage (client) envoie une action au serveur de jeu (serveur API). Le serveur traite la requête, met à jour le monde, et te renvoie l'état actuel. Chaque requête est indépendante — le serveur ne se souvient pas de ta session précédente (stateless). Comme un système de matchmaking : tu envoies ta requête, tu reçois ton lobby.

REST est stateless : chaque requête doit contenir TOUTES les infos nécessaires (auth token inclus).
API = Application Programming Interface. REST = style archi. HTTP = protocole de transport. JSON = format de données. Ces 4 concepts sont distincts mais travaillent ensemble.
CONCEPTS CLÉS
📡 HTTP / HTTPS

HTTP (HyperText Transfer Protocol) est le protocole de communication du web. HTTPS = HTTP + chiffrement TLS/SSL.

Structure d'une requête : méthode + URL + headers + body (optionnel).

HTTPS chiffre les données en transit — sans lui, n'importe qui sur le réseau peut lire tes tokens et passwords.
🎯 MÉTHODES HTTP

Les méthodes définissent l'action à effectuer sur une ressource :

GET lire · POST créer · PUT remplacer · PATCH modifier · DELETE supprimer

GET et DELETE n'ont pas de body. POST, PUT et PATCH si.
📊 STATUS CODES

Le serveur répond avec un code à 3 chiffres :

2xx succès · 3xx redirection · 4xx erreur client · 5xx erreur serveur

Un 200 OK ne veut pas dire que la logique métier a réussi — vérifie toujours le body de la réponse.
📝 JSON

JSON (JavaScript Object Notation) est le format d'échange standard des APIs REST. C'est du texte lisible par les humains et parsable par les machines.

// Structure JSON type
{
  "id": 42,
  "name": "Adam",
  "tags": ["dev", "holberton"]
}
🔐 AUTHENTIFICATION

Trois mécanismes principaux pour sécuriser une API :

API Key — clé secrète dans le header · JWT — token signé · OAuth 2.0 — délégation d'accès

Toujours mettre l'auth dans le header Authorization, jamais dans l'URL.
📖 ENDPOINTS & RESSOURCES

Un endpoint est une URL qui représente une ressource. Convention REST :

/users — collection · /users/42 — ressource unique · /users/42/posts — sous-ressource

Les endpoints REST sont des noms (nouns), jamais des verbes. Pas /getUser mais /users/42 avec GET.
CONSOMMER UNE API
💻 AVEC CURL (TERMINAL)

La façon la plus directe de tester une API depuis le terminal.

# GET — récupérer une ressource
curl https://jsonplaceholder.typicode.com/users/1

# GET avec headers et pretty print
curl -s -H "Accept: application/json" \
     https://jsonplaceholder.typicode.com/posts | python3 -m json.tool

# POST — créer une ressource
curl -X POST \
     -H "Content-Type: application/json" \
     -d '{"title": "Boss Fight", "userId": 1}' \
     https://jsonplaceholder.typicode.com/posts

# DELETE — supprimer
curl -X DELETE https://jsonplaceholder.typicode.com/posts/1

# Avec API Key dans le header
curl -H "Authorization: Bearer TON_TOKEN" \
     https://api.exemple.com/protected
Utilise -v pour voir les headers complets (verbose). -i inclut les headers de réponse.
🐍 AVEC PYTHON — MODULE requests

La bibliothèque requests rend la consommation d'API élégante en Python.

import requests

# GET simple
response = requests.get("https://jsonplaceholder.typicode.com/users/1")
print(response.status_code)    # 200
print(response.json())          # dict Python

# GET avec paramètres de query
params = {"userId": 1, "_limit": 3}
r = requests.get("https://jsonplaceholder.typicode.com/posts",
                params=params)

# POST avec JSON body
data = {"title": "New Quest", "body": "Kill the dragon", "userId": 1}
r = requests.post("https://jsonplaceholder.typicode.com/posts",
                  json=data)   # json= encode auto en JSON

# Avec headers d'auth
headers = {"Authorization": "Bearer MON_TOKEN"}
r = requests.get("https://api.exemple.com/me", headers=headers)

# Gestion d'erreur robuste
if r.status_code == 200:
    print(r.json())
else:
    print(f"Erreur {r.status_code}: {r.text}")
Ne jamais hardcoder un token ou une clé API dans le code. Utilise des variables d'environnement.
DÉVELOPPER UNE API
🔧 AVEC http.server (PYTHON BUILT-IN)

Pour un serveur HTTP minimal sans dépendances externes — idéal pour comprendre les bases.

from http.server import BaseHTTPRequestHandler, HTTPServer
import json

class SimpleAPI(BaseHTTPRequestHandler):

    def do_GET(self):
        if self.path == '/users':
            data = [{"id": 1, "name": "Adam"}]
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(json.dumps(data).encode())
        else:
            self.send_response(404)
            self.end_headers()

    def do_POST(self):
        length = int(self.headers['Content-Length'])
        body = json.loads(self.rfile.read(length))
        self.send_response(201)  # 201 Created
        self.end_headers()

HTTPServer(('', 8080), SimpleAPI).serve_forever()
⚡ AVEC FLASK

Flask rend le développement d'API REST simple et lisible avec un système de routing élégant.

from flask import Flask, jsonify, request, abort

app = Flask(__name__)

# Données en mémoire (remplacer par DB en prod)
users = [{"id": 1, "name": "Adam"}]

# GET /api/users — liste tous les users
@app.route('/api/users', methods=['GET'])
def get_users():
    return jsonify(users), 200

# GET /api/users/<id> — un seul user
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = next((u for u in users if u["id"] == user_id), None)
    if not user:
        abort(404)
    return jsonify(user), 200

# POST /api/users — créer un user
@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()
    if not data or 'name' not in data:
        abort(400)   # Bad Request
    new_user = {"id": len(users) + 1, "name": data["name"]}
    users.append(new_user)
    return jsonify(new_user), 201  # 201 Created

# DELETE /api/users/<id>
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
    global users
    users = [u for u in users if u["id"] != user_id]
    return '', 204  # 204 No Content

if __name__ == '__main__':
    app.run(debug=True, port=5000)
Utilise flask run en dev. En prod, passe par gunicorn ou uWSGI.
RÉFÉRENCE RAPIDE
⚡ MÉTHODES HTTP
MÉTHODEACTIONBODYIDEMPOTENT
GETLireNonOui
POSTCréerOuiNon
PUTRemplacerOuiOui
PATCHModifierOuiNon
DELETESupprimerNonOui
📊 STATUS CODES ESSENTIELS
CODESIGNIFICATION
200OK — succès
201Created — ressource créée
204No Content — succès sans body
301Moved Permanently
400Bad Request — données invalides
401Unauthorized — non authentifié
403Forbidden — pas les droits
404Not Found — ressource absente
429Too Many Requests — rate limit
500Internal Server Error
BOSS FIGHTS — EXERCICES PROGRESSIFS
⭐ BOSS 1 — LVL 1 FACILE
🗡️ EXPLORER UNE API PUBLIQUE

Utilise curl pour interroger l'API JSONPlaceholder et affiche le résultat formaté.

# 1. Récupère tous les posts de l'user 1
curl -s "https://jsonplaceholder.typicode.com/posts?userId=1" \
     | python3 -m json.tool

# 2. Affiche seulement les titres avec Python
python3 -c "
import requests
posts = requests.get('https://jsonplaceholder.typicode.com/posts', params={'userId': 1}).json()
for p in posts:
    print(f\"  {p['id']}. {p['title']}\")
"
JSONPlaceholder est une API publique gratuite pour s'entraîner — aucune clé nécessaire.
⭐⭐ BOSS 2 — LVL 2 INTERMÉDIAIRE
⚔️ CRÉER UN SERVEUR FLASK CRUD

Implémente une API Flask complète avec GET, POST et DELETE sur une liste de joueurs.

from flask import Flask, jsonify, request, abort
app = Flask(__name__)
players = []

@app.route('/players', methods=['GET'])
def list_players():
    return jsonify(players)

@app.route('/players', methods=['POST'])
def add_player():
    body = request.get_json() or {}
    if 'name' not in body:
        abort(400)
    player = {"id": len(players) + 1, "name": body["name"], "score": 0}
    players.append(player)
    return jsonify(player), 201

# Test avec curl :
# curl -X POST -H "Content-Type: application/json"
#      -d '{"name": "Adam"}' http://localhost:5000/players
⭐⭐⭐ BOSS FINAL — LVL 3 HOLBERTON STYLE
🏆 API SÉCURISÉE AVEC AUTH TOKEN

Ajoute une authentification par API key à ton serveur Flask — accès refusé sans token valide.

from flask import Flask, jsonify, request, abort
import os

app = Flask(__name__)
API_KEY = os.environ.get("API_KEY", "dev-secret-key")

def require_auth():
    """Vérifie le Bearer token dans le header Authorization."""
    auth = request.headers.get("Authorization", "")
    if not auth.startswith("Bearer "):
        abort(401)   # Unauthorized
    token = auth[7:]       # retire "Bearer "
    if token != API_KEY:
        abort(403)   # Forbidden

@app.route('/api/secret')
def secret_endpoint():
    require_auth()
    return jsonify({"message": "Access granted. Welcome, elite player."})

# Tester :
# API_KEY=mykey flask run
# curl -H "Authorization: Bearer mykey" http://localhost:5000/api/secret

# Tester sans token (doit retourner 401) :
# curl http://localhost:5000/api/secret
En prod, utilise python-dotenv pour charger API_KEY depuis un fichier .env — ne committe jamais les secrets.
ERREURS CLASSIQUES
💀 MAUVAISES PRATIQUES
# ❌ Verbes dans les endpoints
GET /getUsers
POST /createUser
DELETE /deleteUser/1

# ❌ Token hardcodé dans le code
API_KEY = "sk-123abc-secret"

# ❌ Pas de gestion d'erreur
r = requests.get(url)
data = r.json()  # crash si 404 !

# ❌ Mauvais status code
return jsonify({"error": "not found"}), 200
# → doit être 404
✅ BONNES PRATIQUES
# ✅ Noms de ressources (nouns)
GET /users
POST /users
DELETE /users/1

# ✅ Variable d'environnement
import os
API_KEY = os.environ.get("API_KEY")

# ✅ Gestion d'erreur propre
r = requests.get(url)
r.raise_for_status()  # lève si 4xx/5xx
data = r.json()

# ✅ Status code sémantique
return jsonify({"error": "not found"}), 404
💀 ERREURS CURL FRÉQUENTES
# ❌ Oublier Content-Type sur POST
curl -X POST -d '{"name":"test"}' /api/users
# → Flask reçoit None dans get_json()

# ❌ Confondre -d et --data-binary
# -d encode le body comme form data
# --data-binary envoie les données telles quelles

# ❌ Guillemets simples sur Windows
# Windows CMD ne supporte pas ' dans -d
# → utiliser PowerShell ou fichier JSON
✅ CURL CORRECT
# ✅ Header Content-Type obligatoire
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"name": "test"}' \
  http://localhost:5000/api/users

# ✅ Fichier JSON pour éviter les issues
curl -X POST \
  -H "Content-Type: application/json" \
  -d @data.json \
  http://localhost:5000/api/users

# ✅ -v pour déboguer les headers
curl -v http://localhost:5000/api/users
SKILL TREE — RÈGLES CRITIQUES
🔑
Stateless obligatoire — Chaque requête REST doit être autonome. Le serveur ne stocke pas l'état du client entre deux requêtes. L'auth token doit être renvoyé à chaque appel.
📝
Content-Type dans CHAQUE POST/PUT/PATCH — Sans Content-Type: application/json, Flask ne parse pas le body. C'est l'erreur #1 des débutants.
🔢
Status code sémantique — Toujours retourner le bon code : 201 pour une création, 204 pour un delete réussi, 404 si la ressource n'existe pas. Jamais 200 pour tout.
🔐
Secrets dans les variables d'env — API keys, tokens, passwords → os.environ.get() ou python-dotenv. Jamais dans le code, jamais dans les URLs.
📖
Noms de ressources au pluriel — Convention REST : /users, /posts, /orders. Jamais de verbes comme /getUsers ou /deletePost.
🛡️
Valider les inputs côté serveur — Ne jamais faire confiance aux données du client. Vérifie que les champs requis existent et ont le bon type avant de traiter.
RÉSUMÉ — CARTE MENTALE
══════════════════════════════════════════════════════════════
                    RESTFUL API — CARTE MENTALE
══════════════════════════════════════════════════════════════

CLIENT ──────── HTTP REQUEST ────────► SERVER
  │                                      │
  │  Méthodes :                          │  Logique :
  │  GET    → lire une ressource         │  Valide le body
  │  POST   → créer                      │  Authentifie
  │  PUT    → remplacer                  │  Interroge la DB
  │  PATCH  → modifier partiellement     │  Retourne JSON
  │  DELETE → supprimer                  │
  │                                      │
  ◄─────────── HTTP RESPONSE ───────────┘
       Status code + JSON body

──────────────────────────────────────────────────────────────
STATUS CODES (à retenir absolument) :
  200 OK       ← GET/PUT réussi
  201 Created  ← POST réussi (nouvelle ressource créée)
  204 No Content ← DELETE réussi
  400 Bad Request ← body invalide ou champ manquant
  401 Unauthorized ← pas de token
  403 Forbidden ← token valide mais pas les droits
  404 Not Found ← ressource inexistante
  500 Server Error ← bug côté serveur

──────────────────────────────────────────────────────────────
CONSOMMER (Python requests) :
  r = requests.get(url, headers={"Authorization": "Bearer ..."})
  r.raise_for_status()    # lève si 4xx ou 5xx
  data = r.json()         # parse le JSON en dict Python

──────────────────────────────────────────────────────────────
CRÉER (Flask) :
  @app.route('/resource', methods=['GET', 'POST'])
  def handle():
      if request.method == 'GET':
          return jsonify(data), 200
      body = request.get_json() or abort(400)
      # créer + return jsonify(new), 201

══════════════════════════════════════════════════════════════