Gestion d'erreurs et codes HTTP
Mis à jour le 29 juillet 2026
Gestion d’erreurs et codes HTTP
En production, les erreurs sont inévitables. Le réseau tombe, la clé expire, un collègue déploie un paramètre invalide, l’API elle-même connaît une panne de trois minutes. Comprendre les codes HTTP retournés par l’API et savoir les gérer correctement est essentiel pour construire des applications robustes. Toute la difficulté tient en une question que vous devrez poser à chaque exception : celle-ci va-t-elle disparaître si je réessaie, ou est-ce que je vais boucler indéfiniment sur un problème que seul un humain peut corriger ?
Les codes HTTP de l’API OpenAI
L’API retourne des codes HTTP standards pour indiquer le résultat de chaque requête. Le SDK Python ne vous laisse pas manipuler ces codes à la main : il traduit chacun d’eux en une exception typée que vous pouvez intercepter séparément. C’est ce qui vous permet d’écrire des blocs except précis plutôt qu’un except Exception fourre-tout qui masquerait la nature du problème.
from openai import OpenAI
from openai import (
APIError,
AuthenticationError,
BadRequestError,
RateLimitError,
APIConnectionError,
InternalServerError,
PermissionDeniedError,
NotFoundError,
UnprocessableEntityError,
)
client = OpenAI()
401 — AuthenticationError
Le 401 signale que la clé API est invalide ou manquante. C’est typiquement ce que vous voyez au premier déploiement, quand la variable d’environnement n’a pas été propagée au conteneur. Réessayer ne sert strictement à rien : la clé sera tout aussi invalide dans dix secondes.
try:
# Clé API invalide ou manquante
bad_client = OpenAI(api_key="sk-invalid")
bad_client.responses.create(
model="gpt-5.6-terra",
input="Test"
)
except AuthenticationError as e:
print(f"Code : {e.status_code}") # 401
print(f"Message : {e.message}")
# Action : vérifier la clé API, la régénérer si nécessaire
400 — BadRequestError
Le 400 vous dit que la requête est mal formée. Ici, une température de 5.0 alors que le maximum accepté est 2.0. Le message d’erreur nomme presque toujours le paramètre fautif, ce qui en fait l’erreur la plus rapide à corriger — à condition de la logger correctement plutôt que de l’avaler silencieusement.
try:
# Paramètres invalides
client.responses.create(
model="gpt-5.6-terra",
input="Test",
temperature=5.0 # Valeur invalide (max 2.0)
)
except BadRequestError as e:
print(f"Code : {e.status_code}") # 400
print(f"Message : {e.message}")
# Action : corriger les paramètres de la requête
429 — RateLimitError
Le 429 indique que vous envoyez trop de requêtes, ou que votre quota est dépassé. Contrairement aux deux précédentes, cette erreur est temporaire par nature : le compteur se réinitialise. C’est le cas d’école du retry, mais d’un retry qui attend — et qui attend de plus en plus longtemps à chaque tentative.
try:
# Trop de requêtes ou quota dépassé
client.responses.create(
model="gpt-5.6-terra",
input="Test"
)
except RateLimitError as e:
print(f"Code : {e.status_code}") # 429
print(f"Message : {e.message}")
# Action : attendre et réessayer avec backoff exponentiel
500/503 — InternalServerError
Les 500 et 503 viennent de chez OpenAI, pas de chez vous. Votre requête était valide, l’infrastructure n’a simplement pas pu la traiter. Réessayez après un délai : dans l’immense majorité des cas, la deuxième ou la troisième tentative passe.
try:
client.responses.create(
model="gpt-5.6-terra",
input="Test"
)
except InternalServerError as e:
print(f"Code : {e.status_code}") # 500 ou 503
print(f"Message : {e.message}")
# Action : réessayer après un délai, l'erreur est côté OpenAI
Gestionnaire d’erreurs complet
Rassemblons maintenant ces comportements dans une seule fonction. La logique est celle du tri : les erreurs fatales sont converties en RuntimeError et propagées immédiatement, les erreurs transitoires déclenchent une attente doublée à chaque tour, et si le budget de tentatives s’épuise, l’appel échoue proprement plutôt que de rendre une valeur vide que l’appelant interpréterait mal.
from openai import OpenAI, APIError, AuthenticationError, RateLimitError
from openai import BadRequestError, APIConnectionError, InternalServerError
import time
client = OpenAI()
def appel_api_robuste(prompt: str, modele: str = "gpt-5.6-terra",
max_retries: int = 3) -> str:
"""Appel API avec gestion complète des erreurs."""
for tentative in range(max_retries):
try:
response = client.responses.create(
model=modele,
input=prompt
)
return response.output_text
except AuthenticationError:
# Ne pas réessayer — la clé est invalide
raise RuntimeError("Clé API invalide. Vérifiez OPENAI_API_KEY.")
except BadRequestError as e:
# Ne pas réessayer — la requête est mal formée
raise RuntimeError(f"Requête invalide : {e.message}")
except RateLimitError:
# Réessayer avec backoff exponentiel
delai = 2 ** tentative
print(f"Rate limit atteint. Attente {delai}s...")
time.sleep(delai)
except InternalServerError:
# Réessayer — erreur côté serveur
delai = 2 ** tentative
print(f"Erreur serveur. Tentative {tentative + 1}/{max_retries}...")
time.sleep(delai)
except APIConnectionError:
# Réessayer — problème réseau
delai = 2 ** tentative
print(f"Erreur connexion. Tentative {tentative + 1}/{max_retries}...")
time.sleep(delai)
raise RuntimeError(f"Échec après {max_retries} tentatives")
# Utilisation
try:
resultat = appel_api_robuste("Bonjour !")
print(resultat)
except RuntimeError as e:
print(f"Erreur fatale : {e}")
Erreurs spécifiques à la Responses API
Deux situations méritent un traitement à part parce qu’elles se présentent sous un code générique tout en appelant une correction très précise.
Contexte trop long
Un prompt qui dépasse la fenêtre de contexte remonte en 400, comme une faute de frappe sur un paramètre. Pour distinguer les deux, inspectez le message : la présence de context_length_exceeded vous oriente vers une troncature du texte ou un changement de modèle plutôt que vers une relecture de vos paramètres.
try:
response = client.responses.create(
model="gpt-5.6-terra",
input="x " * 300000 # Dépasse la fenêtre de contexte
)
except BadRequestError as e:
if "context_length_exceeded" in str(e.message):
print("Le prompt est trop long pour ce modèle.")
print("Solutions : tronquer le texte, ou basculer sur un modèle")
print("dont la fenêtre de contexte est plus large.")
Modèle indisponible
Les identifiants de modèles ne sont pas éternels. Un déploiement qui référence encore un modèle retiré échoue en 404, et l’application entière tombe alors qu’elle fonctionnait la veille. Prévoyez ce cas explicitement : c’est le rappel automatique qu’il faut mettre à jour la configuration.
try:
response = client.responses.create(
model="gpt-4o", # Modèle retiré !
input="Test"
)
except NotFoundError as e:
print(f"Modèle non trouvé : {e.message}")
print("Utilisez gpt-5.6-terra ou gpt-5.6-sol à la place.")
Logging structuré des erreurs
Une erreur non tracée est une erreur que vous découvrirez par le ticket d’un client. Loguer en JSON plutôt qu’en texte libre change tout dès que vous cherchez à agréger : vous pouvez compter les 429 par heure, filtrer sur un response_id ou tracer une dérive de consommation, ce qui est impossible avec des lignes de log rédigées à la main.
import logging
import json
from datetime import datetime
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("openai_client")
def appel_avec_log(prompt: str) -> str:
"""Appel API avec logging structuré."""
request_id = None
try:
response = client.responses.create(
model="gpt-5.6-terra",
input=prompt
)
logger.info(json.dumps({
"event": "api_success",
"model": response.model,
"tokens": response.usage.total_tokens,
"response_id": response.id,
"timestamp": datetime.now().isoformat()
}))
return response.output_text
except APIError as e:
logger.error(json.dumps({
"event": "api_error",
"status_code": e.status_code,
"message": e.message,
"timestamp": datetime.now().isoformat()
}))
raise
resultat = appel_avec_log("Bonjour !")
Tableau récapitulatif
Gardez cette grille sous la main : elle résume la décision à prendre pour chaque code.
| Code | Exception | Réessayer ? | Action |
|---|---|---|---|
| 400 | BadRequestError | Non | Corriger la requête |
| 401 | AuthenticationError | Non | Vérifier la clé API |
| 403 | PermissionDeniedError | Non | Vérifier les permissions |
| 404 | NotFoundError | Non | Vérifier le modèle/endpoint |
| 422 | UnprocessableEntityError | Non | Corriger le format |
| 429 | RateLimitError | Oui | Backoff exponentiel |
| 500 | InternalServerError | Oui | Attendre et réessayer |
| 503 | InternalServerError | Oui | Service temporairement indisponible |
Points clés à retenir
- Catégorisez les erreurs : réessayables (429, 500, 503) vs fatales (400, 401, 403)
- Utilisez le backoff exponentiel pour les erreurs réessayables
- Ne réessayez jamais les erreurs d’authentification ou de requête invalide
- Loguez chaque erreur avec contexte (code, message, timestamp)
- Le SDK gère automatiquement 2 retries — configurez
max_retriessi besoin