L'API Products Manager retourne des erreurs structurées et prévisibles. Chaque réponse d'erreur suit un format JSON uniforme quel que soit l'endpoint ou le type de problème.
Toutes les erreurs retournent un objet JSON avec la structure suivante :
{
"error": {
"code": "PRODUCT_NOT_FOUND",
"message": "The requested product does not exist.",
"details": {
"product_id": 9999
}
}
}
| Champ | Type | Description |
|---|
error.code | string | Code d'erreur applicatif, stable et lisible par machine |
error.message | string | Message lisible par l'humain, en anglais |
error.details | object | Informations contextuelles supplémentaires (optionnel) |
Le champ details peut contenir des champs spécifiques selon l'erreur : identifiant de la ressource introuvable, champs en échec de validation, délai d'attente avant retry, etc.
Codes HTTP Utilisés
| Code HTTP | Signification | Causes typiques |
|---|
400 Bad Request | Requête malformée | Body JSON invalide, paramètre manquant ou de mauvais type |
401 Unauthorized | Non authentifié | Token absent, expiré ou révoqué |
403 Forbidden | Accès refusé | Permission insuffisante, quota dépassé, tenant non autorisé |
404 Not Found | Ressource introuvable | ID inexistant ou appartenant à un autre tenant |
409 Conflict | Conflit de ressource | SKU déjà existant, import déjà en cours |
422 Unprocessable Entity | Erreur de validation | Données structurellement valides mais métier invalides (EAN incorrect, champ obligatoire vide) |
429 Too Many Requests | Rate limit dépassé | Trop de requêtes dans la fenêtre de temps |
500 Internal Server Error | Erreur serveur | Erreur inattendue côté backend |
503 Service Unavailable | Service indisponible | Maintenance, surcharge, provider IA inaccessible |
Codes d'Erreur Applicatifs
Authentification
| Code | HTTP | Description |
|---|
AUTH_TOKEN_EXPIRED | 401 | Le JWT access token a expiré — utiliser /auth/refresh |
AUTH_TOKEN_INVALID | 401 | Token malformé ou signature invalide |
AUTH_TOKEN_REVOKED | 401 | Token révoqué manuellement (déconnexion, rotation) |
AUTH_INVALID_CREDENTIALS | 401 | Email ou mot de passe incorrect |
AUTH_ACCOUNT_DISABLED | 401 | Compte utilisateur désactivé par un admin |
AUTH_INSUFFICIENT_PERMISSIONS | 403 | L'utilisateur n'a pas la permission requise pour cette action |
AUTH_API_KEY_INVALID | 401 | API Key absente, invalide ou révoquée |
AUTH_API_KEY_SCOPE_DENIED | 403 | L'API Key n'a pas le scope requis pour cet endpoint |
Validation
| Code | HTTP | Description |
|---|
VALIDATION_ERROR | 422 | Erreur de validation générique — voir details.fields |
INVALID_EAN | 422 | Code EAN/GTIN invalide (checksum ou longueur incorrecte) |
INVALID_SKU_FORMAT | 422 | Format de SKU non conforme aux règles du tenant |
DUPLICATE_SKU | 409 | Un produit avec ce SKU existe déjà dans le catalogue |
DUPLICATE_EAN | 409 | Un produit avec cet EAN existe déjà |
REQUIRED_FIELD_MISSING | 422 | Champ obligatoire absent dans le body |
INVALID_DATE_FORMAT | 422 | Date non conforme au format ISO 8601 |
FILE_TOO_LARGE | 400 | Fichier uploadé dépasse la limite autorisée |
UNSUPPORTED_FILE_FORMAT | 400 | Format de fichier non supporté pour cet endpoint |
Quotas Multi-tenant
| Code | HTTP | Description |
|---|
QUOTA_PRODUCTS_EXCEEDED | 403 | Le nombre maximum de produits du plan est atteint |
QUOTA_IMPORTS_EXCEEDED | 403 | Le quota mensuel d'imports est épuisé |
QUOTA_EXPORTS_EXCEEDED | 403 | Le quota mensuel d'exports est épuisé |
QUOTA_CREDITS_EXHAUSTED | 403 | Les crédits IA du tenant sont épuisés |
QUOTA_CONNECTORS_EXCEEDED | 403 | Le nombre maximum de connecteurs actifs est atteint |
QUOTA_USERS_EXCEEDED | 403 | La limite d'utilisateurs du plan est atteinte |
PLAN_FEATURE_UNAVAILABLE | 403 | La fonctionnalité n'est pas incluse dans le plan actuel |
Ressources Introuvables
| Code | HTTP | Description |
|---|
PRODUCT_NOT_FOUND | 404 | Produit introuvable pour l'ID fourni |
SUPPLIER_NOT_FOUND | 404 | Fournisseur introuvable |
IMPORT_NOT_FOUND | 404 | Session d'import introuvable |
EXPORT_NOT_FOUND | 404 | Export introuvable |
CONNECTOR_NOT_FOUND | 404 | Connecteur introuvable |
TENANT_NOT_FOUND | 404 | Tenant introuvable ou accès non autorisé |
USER_NOT_FOUND | 404 | Utilisateur introuvable |
MEDIA_NOT_FOUND | 404 | Fichier média introuvable |
Logique Métier
| Code | HTTP | Description |
|---|
IMPORT_ALREADY_RUNNING | 409 | Un import est déjà en cours pour ce fournisseur |
IMPORT_INVALID_MAPPING | 422 | Le mapping de colonnes ne correspond pas au fichier |
ENRICHMENT_PROVIDER_UNAVAILABLE | 503 | Le provider d'IA sélectionné est temporairement indisponible |
ENRICHMENT_JOB_NOT_FOUND | 404 | Job d'enrichissement introuvable |
CONNECTOR_AUTH_FAILED | 422 | Échec d'authentification auprès de la plateforme externe |
CONNECTOR_SYNC_IN_PROGRESS | 409 | Une synchronisation est déjà en cours sur ce connecteur |
PRICE_MONITOR_NO_COMPETITORS | 422 | Aucun concurrent configuré pour ce produit |
COMPLIANCE_MISSING_SUBSTANCES | 422 | Données substances manquantes pour générer la fiche REACH |
Rate Limiting
| Code | HTTP | Description |
|---|
RATE_LIMIT_EXCEEDED | 429 | Limite de requêtes dépassée — voir header Retry-After |
Exemples de Réponses d'Erreur
401 — Token expiré
{
"error": {
"code": "AUTH_TOKEN_EXPIRED",
"message": "Access token has expired. Please refresh your token.",
"details": {
"expired_at": "2025-11-15T12:00:00Z"
}
}
}
403 — Permission insuffisante
{
"error": {
"code": "AUTH_INSUFFICIENT_PERMISSIONS",
"message": "You do not have permission to perform this action.",
"details": {
"required_permission": "products:delete",
"user_role": "viewer"
}
}
}
422 — Erreur de validation (champs multiples)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "One or more fields failed validation.",
"details": {
"fields": {
"ean": "Invalid EAN-13 checksum.",
"price": "Must be a positive number.",
"name": "This field is required."
}
}
}
}
409 — SKU dupliqué
{
"error": {
"code": "DUPLICATE_SKU",
"message": "A product with SKU 'PROD-001' already exists in this catalog.",
"details": {
"sku": "PROD-001",
"existing_product_id": 4821
}
}
}
403 — Quota dépassé
{
"error": {
"code": "QUOTA_PRODUCTS_EXCEEDED",
"message": "Your plan allows a maximum of 10,000 products. Upgrade your plan to add more.",
"details": {
"current_count": 10000,
"plan_limit": 10000,
"plan": "starter",
"upgrade_url": "https://productsmanager.app/billing/upgrade"
}
}
}
429 — Rate limit
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please wait before retrying.",
"details": {
"retry_after_seconds": 37,
"limit": "100 requests per minute",
"reset_at": "2025-11-15T10:31:00Z"
}
}
}
503 — Provider IA indisponible
{
"error": {
"code": "ENRICHMENT_PROVIDER_UNAVAILABLE",
"message": "The AI enrichment provider is temporarily unavailable. Please retry in a few minutes.",
"details": {
"provider": "openai",
"retry_after_seconds": 120
}
}
}
Gestion des Erreurs côté Client
Stratégie générale
- 4xx : erreurs client — corriger la requête avant de réessayer (sauf
429) - 429 : attendre la durée indiquée dans
Retry-After avant de réessayer - 5xx : erreurs serveur — réessayer avec backoff exponentiel
Retry avec Backoff Exponentiel (429 et 503)
Pour les erreurs 429 Too Many Requests et 503 Service Unavailable, implémentez un backoff exponentiel avec jitter :
import time
import random
def request_with_backoff(fn, max_retries=5):
for attempt in range(max_retries):
response = fn()
if response.status_code not in (429, 503):
return response
retry_after = int(response.headers.get("Retry-After", 2 ** attempt))
jitter = random.uniform(0, 1)
wait = retry_after + jitter
time.sleep(wait)
raise Exception("Max retries exceeded")
Idempotency Keys
Pour les opérations qui créent ou modifient des ressources (POST, PATCH), utilisez le header Idempotency-Key avec un UUID unique par tentative logique. Si la requête est rejouée avec la même clé dans les 24 heures, l'API retourne la réponse originale sans re-exécuter l'opération :
curl -X POST "https://api.productsmanager.app/api/v1/imports" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{"supplier_id": 42, "file_url": "https://..."}'
Ceci est particulièrement utile pour les imports et les créations en masse afin d'éviter les doublons en cas de timeout réseau ou d'erreur transitoire.
Documentation Associée