Guide pratique : gérer les erreurs d'API

Objectif : mettre en place une gestion des erreurs et une logique de réessai robustes pour les intégrations d'API en production

Durée estimée : 30 à 45 minutes pour la mise en œuvre

Quand l'utiliser : pour mettre en place des intégrations de production capables de gérer de manière transparente les problèmes réseau, l'expiration des authentifications et les interruptions de service.

Codes de retour de l'API

Coder Descriptif Opération
200 Succès (GET) Données de réponse du processus
201 La création a abouti La ressource a été créée avec succès
202 La mise à jour a abouti La ressource a été mise à jour avec succès
400 Chaîne API incorrecte / Demande non valide Vérifier le format, les paramètres et le corps de la requête de l' URL
401 Non autorisé Réauthentifiez-vous et réessayez
403 Interdit Vérifier les autorisations des utilisateurs
404 Non trouvé Vérifier que la ressource existe (projet, tableau, période)
500 Erreur interne du serveur Réessayez avec un délai d'attente exponentiel; contactez le service d'assistance si le problème persiste

Comprendre les réponses d'erreur

Les réponses d'erreur renvoient un objet JSON contenant un champ « message » qui décrit le problème :

{
 "message": "Poorly formatted date string: 'SOMEDATE:2010'. Ought to be of the form 'granularity':'year'."
}

Messages d'erreur courants :

Modèle de message Cause La solution
Chaîne de date mal formatée Format de période non valide Utilisez le format Mois:Année (par exemple, January:2024 )
L'utilisateur X n'existe pas Nom d'utilisateur non valide dans la requête Vérifier le format du nom d'utilisateur et l'existence du compte
Accès refusé Droits insuffisants Vérifier que le compte dispose des ensembles d'autorisations requis
Table introuvable La table cible n'existe pas Vérifier l'orthographe du nom de la table et le projet

Mise en œuvre de la logique de nouvelle tentative

Les intégrations en production doivent mettre en œuvre un délai d'attente exponentiel en cas de défaillances temporaires :
import requests
import time
import logging
 
class TBMStudioAPIClient:
 """Production-ready TBM Studio API client with retry logic."""
 
 def __init__(self, customer_id, domain, max_retries=3, base_delay=1.0):
 self.customer_id = customer_id
 self.domain = domain
 self.max_retries = max_retries
 self.base_delay = base_delay
 self.token = None
 self.env_id = None
 self.logger = logging.getLogger(__name__)
 
 def authenticate(self, public_key, secret_key):
 """Authenticate and store token."""
 url = "https://frontdoor.apptio.com/service/apikeylogin"
 response = self._make_request(
 "POST", url,
 json={"keyAccess": public_key, "keySecret": secret_key},
 headers={"Content-Type": "application/json"}
 )
 self.token = response.cookies.get('apptio-opentoken')
 
 # Get environment ID
 env_url = f"https://frontdoor.apptio.com/api/environment/{self.domain}/main"
 env_response = self._make_request("GET", env_url)
 self.env_id = env_response.json()["id"]
 
 def _make_request(self, method, url, **kwargs):
 """Make HTTP request with retry logic."""
 last_exception = None
 
 for attempt in range(self.max_retries):
 try:
 # Add auth headers if authenticated
 if self.token and 'headers' not in kwargs:
 kwargs['headers'] = {}
 if self.token:
 kwargs['headers'].update({
 "apptio-opentoken": self.token,
 "apptio-current-environment": str(self.env_id) if self.env_id else "",
 "app-type": "Flagship",
 "app-version": "NA"
 })
 
 response = requests.request(method, url, **kwargs)
 
 # Check for retryable status codes
 if response.status_code == 401:
 self.logger.warning("Token expired, re-authentication required")
 raise AuthenticationError("Token expired")
 
 if response.status_code >= 500:
 response.raise_for_status() # Will be caught and retried
 
 response.raise_for_status()
 return response
 
 except requests.exceptions.RequestException as e:
 last_exception = e
 
 if attempt < self.max_retries - 1:
 delay = self.base_delay * (2 ** attempt) # Exponential backoff
 self.logger.warning(
 f"Request failed (attempt {attempt + 1}/{self.max_retries}), "
 f"retrying in {delay}s: {e}"
 )
 time.sleep(delay)
 
 raise last_exception
 
 def upload(self, project, table, time_period, file_path, action="overwrite"):
 """Upload data with error handling."""
 import urllib.parse
 
 project_enc = urllib.parse.quote(project)
 table_enc = urllib.parse.quote(table)
 
 url = (f"https://{self.customer_id}.apptio.com/biit/api/v1/"
 f"{self.domain}/{project_enc}/{table_enc}/{time_period}/{action}")
 
 with open(file_path, 'rb') as f:
 response = self._make_request("POST", url, files={'myfile': f})
 
 return response.json()
 
 
class AuthenticationError(Exception):
 """Raised when authentication fails or token expires."""
 pass
 
 
# Usage with error handling
def main():
 logging.basicConfig(level=logging.INFO)
 
 client = TBMStudioAPIClient("acme", "acme.com")
 
 try:
 client.authenticate("public_key", "secret_key")
 result = client.upload(
 project="Cost Transparency",
 table="GL Data",
 time_period="January:2024",
 file_path="data.csv"
 )
 print(f"Upload successful: {result}")
 
 except AuthenticationError:
 print("Authentication failed. Check API credentials.")
 except requests.exceptions.HTTPError as e:
 print(f"API error: {e.response.status_code} - {e.response.text}")
 except Exception as e:
 print(f"Unexpected error: {e}")

Stratégie de rafraîchissement des jetons

Pour les processus de longue durée, mettez en place un rafraîchissement proactif des jetons :
import time
from datetime import datetime, timedelta
 
class TokenManager:
 """Manage API token lifecycle."""
 
 def __init__(self, client, refresh_margin_minutes=5):
 self.client = client
 self.refresh_margin = timedelta(minutes=refresh_margin_minutes)
 self.token_expiry = None
 self.token_lifetime = timedelta(hours=1) # Adjust based on actual expiry
 
 def get_valid_token(self, public_key, secret_key):
 """Get a valid token, refreshing if necessary."""
 now = datetime.now()
 
 if (self.token_expiry is None or 
 now >= self.token_expiry - self.refresh_margin):
 self.client.authenticate(public_key, secret_key)
 self.token_expiry = now + self.token_lifetime
 
 return self.client.token

Bonnes pratiques en matière d'exploitation forestière

  • Enregistrer tous les appels API : inclure l'horodatage, le point de terminaison, l' HTTP la méthode et le statut de la réponse
  • Enregistrer les temps de réponse : surveiller toute baisse de performances
  • Ne jamais enregistrer les identifiants : masquer les jetons, les clés API et les mots de passe dans les journaux
  • Enregistrer les réponses d'erreur : capturer l'intégralité des messages d'erreur à des fins de dépannage
  • Utilisez la journalisation structurée : formatez les journaux au format JSON pour faciliter leur analyse et leur interprétation
Avertissement : évitez d'enregistrer des données sensibles telles que des clés API, des jetons ou des informations personnelles identifiables. Utilisez des espaces réservés ou un hachage pour les informations identifiables dans les journaux.

Pièges courants

  • Pas de nouvelle tentative en cas d'erreurs 500 : les erreurs serveur sont souvent temporaires. Veillez à toujours mettre en place une nouvelle tentative avec un délai d'attente.
  • Non-respect des limites de débit : bien que cela ne soit pas explicitement indiqué, évitez d'envoyer des requêtes à un rythme trop soutenu. Ajouter des délais entre les opérations par lots.
  • Absence de gestion de l'expiration des jetons : traitez toujours les réponses 401 en procédant à une nouvelle authentification et en réessayant.
  • Analyse d'erreur manquante : analysez et consignez systématiquement le champ « message » des réponses d'erreur afin d'obtenir des diagnostics pertinents.