> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oneclickdz.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Gestion des Erreurs

> Guide complet pour gérer efficacement les erreurs avec l'API Flexy

## Structure de la Réponse d'Erreur

Toutes les erreurs suivent un format cohérent :

```json theme={null}
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "details": {
      // additional context
    }
  },
  "requestId": "req_abc123"
}
```

## Codes de Statut HTTP

| Statut | Catégorie        | Description                                             |
| ------ | ---------------- | ------------------------------------------------------- |
| `400`  | Erreur Client    | Paramètres de requête invalides ou erreur de validation |
| `401`  | Authentification | Clé API invalide ou manquante                           |
| `403`  | Autorisation     | Permissions ou solde insuffisants                       |
| `404`  | Non Trouvé       | La ressource n'existe pas                               |
| `429`  | Limite de Taux   | Trop de requêtes                                        |
| `500`  | Erreur Serveur   | Erreur interne du serveur                               |
| `503`  | Indisponible     | Service temporairement indisponible                     |

## Codes d'Erreur Courants

<AccordionGroup>
  <Accordion title="Erreurs d'Authentification (401)">
    ### MISSING\_ACCESS\_TOKEN

    * **Message** : Access token is required
    * **Cause** : L'en-tête `X-Access-Token` est manquant
    * **Action** : Incluez votre clé API dans l'en-tête

    ```javascript theme={null}
    // ✅ Correct
    fetch(url, {
      headers: { 'X-Access-Token': 'YOUR_API_KEY' }
    })
    ```

    ### INVALID\_ACCESS\_TOKEN / ERR\_AUTH

    * **Message** : The provided access token is invalid
    * **Cause** : La clé API est incorrecte, expirée ou révoquée
    * **Action** :
      * Vérifiez que la clé API est correcte
      * Générez une nouvelle clé si nécessaire
      * Ne journalisez pas les valeurs de clé sensibles
      * Contactez le support si le problème persiste
  </Accordion>

  <Accordion title="Erreurs d'Autorisation (403)">
    ### NO\_BALANCE / INSUFFICIENT\_BALANCE

    * **Message** : Insufficient balance
    * **Cause** : Le solde du compte est trop faible
    * **Action** :
      * Vérifiez le solde via `/v3/account/balance` avant les opérations
      * Affichez le solde actuel à l'utilisateur
      * Proposez une option de rechargement
      * N'effectuez pas de nouvelle tentative sans avoir ajouté des fonds

    ```javascript theme={null}
    const balanceRes = await fetch('https://api.oneclickdz.com/v3/account/balance', {
      headers: { 'X-Access-Token': API_KEY }
    });
    const { data } = await balanceRes.json();

    if (data.balance < requiredAmount) {
      throw new Error('Insufficient balance');
    }
    ```

    ### DUPLICATED\_REF

    * **Message** : This reference ID is already in use
    * **Cause** : La référence a déjà été utilisée dans une requête précédente
    * **Action** :
      * Vérifiez le statut de la commande existante
      * Générez une nouvelle référence unique
      * Ne créez pas de commandes en double

    ```javascript theme={null}
    const ref = `order-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    ```

    ### IP\_BLOCKED

    * **Message** : Your IP has been temporarily blocked
    * **Cause** : Trop de tentatives d'authentification échouées
    * **Action** :
      * Attendez 15 minutes pour le déblocage automatique
      * Vérifiez la clé API correcte
      * Contactez le support si le problème persiste

    ### IP\_NOT\_ALLOWED

    * **Message** : Your IP address is not whitelisted
    * **Cause** : La liste blanche d'IP est activée
    * **Action** : Ajoutez votre IP à la liste blanche dans le tableau de bord
  </Accordion>

  <Accordion title="Erreurs de Validation (400)">
    ### ERR\_VALIDATION

    * **Message** : Validation error
    * **Cause** : Les paramètres de requête ne respectent pas les exigences
    * **Problèmes Courants** : Champs manquants, types de données invalides, non-correspondance de motif
    * **Action** :
      * Validez les entrées côté client en premier
      * Vérifiez `error.details` pour les problèmes de champs spécifiques
      * Ne réessayez pas sans corriger le problème

    ```javascript theme={null}
    // Validate before sending
    function validateTopupRequest(data) {
      const errors = [];
      
      if (!data.plan_code) {
        errors.push({ field: 'plan_code', message: 'Plan code is required' });
      }
      
      if (!data.MSSIDN || !/^0[567][0-9]{8}$/.test(data.MSSIDN)) {
        errors.push({ field: 'MSSIDN', message: 'Invalid phone number format' });
      }
      
      if (isDynamicPlan(data.plan_code)) {
        if (!data.amount || data.amount < 50 || data.amount > 5000) {
          errors.push({ field: 'amount', message: 'Amount must be between 50 and 5000' });
        }
      }
      
      return errors.length > 0 ? { valid: false, errors } : { valid: true };
    }
    ```

    ### ERR\_PHONE

    * **Message** : Invalid phone number
    * **Cause** : Le numéro de téléphone est incorrect ou n'existe pas
    * **Action** : Utilisez `/v3/internet/check-number` pour valider en premier

    ```javascript theme={null}
    const checkRes = await fetch(
      `https://api.oneclickdz.com/v3/internet/check-number?type=ADSL&number=${number}`,
      { headers: { 'X-Access-Token': API_KEY } }
    );

    if (!checkRes.ok) {
      throw new Error('Invalid phone number');
    }
    ```

    ### ERR\_STOCK

    * **Message** : Product out of stock
    * **Cause** : Le produit/la valeur de carte demandé(e) n'est pas disponible
    * **Action** :
      * Vérifiez le stock avant de commander
      * Proposez des dénominations alternatives
      * Réessayez ultérieurement
  </Accordion>

  <Accordion title="Erreurs Non Trouvé (404)">
    ### NOT\_FOUND - **Message** : Resource not found - **Cause** : La ressource

    demandée n'existe pas - **Cas Courants** : ID de commande invalide, référence
    invalide, ressource supprimée - **Action** : - Vérifiez que l'ID/la référence
    est correct(e) - Vérifiez les fautes de frappe - Gérez de manière élégante
    dans l'interface utilisateur
  </Accordion>

  <Accordion title="Erreurs de Limite de Taux (429)">
    ### RATE\_LIMIT\_EXCEEDED

    * **Message** : Too many requests
    * **Cause** : La limite de taux a été dépassée
    * **Limites** : Sandbox : 60 req/min, Production : 120 req/min
    * **Action** : Implémentez un backoff exponentiel avec une logique de nouvelle tentative

    ```javascript theme={null}
    async function fetchWithRetry(url, options, maxRetries = 3) {
      for (let i = 0; i < maxRetries; i++) {
        const response = await fetch(url, options);
        
        if (response.status === 429) {
          const retryAfter = response.headers.get('Retry-After') || Math.pow(2, i);
          await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
          continue;
        }
        
        return response;
      }
      
      throw new Error('Max retries exceeded');
    }
    ```
  </Accordion>

  <Accordion title="Erreurs Serveur (500/503)">
    ### INTERNAL\_SERVER\_ERROR / INTERNAL\_ERROR

    * **Message** : Developer was notified and will check shortly
    * **Cause** : Erreur inattendue sur nos serveurs
    * **Action** :
      * **Ne remboursez pas immédiatement - attendez 24 heures**
      * Sauvegardez le requestId pour le support
      * Implémentez une logique de nouvelle tentative avec backoff
      * Contactez le support avec les détails
      * Nous sommes automatiquement notifiés

    ```javascript theme={null}
    if (response.status === 500) {
      console.error('Server error:', data.requestId);
      // Mark for review - don't refund yet
      await scheduleStatusCheck(orderId, 24 * 60 * 60 * 1000);
    }
    ```

    ### ERR\_SERVICE

    * **Message** : Service temporarily unavailable
    * **Cause** : Maintenance du service ou problème temporaire
    * **Action** :
      * Affichez un message de maintenance
      * Réessayez après un délai
      * Surveillez la résolution
  </Accordion>
</AccordionGroup>

## Bonnes Pratiques d'Implémentation

### 1. Vérifiez Toujours le Champ success

```javascript theme={null}
const response = await fetch(url, options);
const data = await response.json();

if (data.success) {
  return data.data;
} else {
  throw new Error(`${data.error.code}: ${data.error.message}`);
}
```

### 2. Gérez les Codes d'Erreur Spécifiques

```javascript theme={null}
async function sendTopUp(params) {
  const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Access-Token": API_KEY,
    },
    body: JSON.stringify(params),
  });

  const data = await response.json();

  if (!data.success) {
    switch (data.error.code) {
      case "NO_BALANCE":
      case "INSUFFICIENT_BALANCE":
        throw new Error("Insufficient balance. Please add funds.");

      case "DUPLICATED_REF":
        return await checkOrderStatus(params.ref);

      case "ERR_VALIDATION":
        throw new Error(`Invalid input: ${data.error.message}`);

      case "INTERNAL_SERVER_ERROR":
      case "INTERNAL_ERROR":
        await scheduleStatusCheck(params.ref);
        throw new Error("Temporary error, checking status later");

      default:
        throw new Error(`${data.error.code}: ${data.error.message}`);
    }
  }

  return data.data;
}
```

### 3. Logique de Nouvelle Tentative Intelligente avec Backoff Exponentiel

```javascript theme={null}
async function safeApiCall(apiFunction, options = {}) {
  const { maxRetries = 3, retryDelay = 1000, retryOn = [500, 503] } = options;

  let lastError;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await apiFunction();
    } catch (error) {
      lastError = error;

      // Don't retry on client errors (4xx)
      if (error.status >= 400 && error.status < 500) {
        throw error;
      }

      // Retry on specific status codes
      if (retryOn.includes(error.status) && attempt < maxRetries) {
        const delay = retryDelay * Math.pow(2, attempt - 1);
        console.log(`Retry ${attempt}/${maxRetries} after ${delay}ms`);
        await new Promise((resolve) => setTimeout(resolve, delay));
        continue;
      }

      throw error;
    }
  }

  throw lastError;
}

// Usage
try {
  const result = await safeApiCall(() => sendMobileTopup(data), {
    maxRetries: 3,
    retryDelay: 2000,
  });
} catch (error) {
  handleError(error);
}
```

### 4. Messages d'Erreur Conviviaux

```javascript theme={null}
const errorMessages = {
  MISSING_ACCESS_TOKEN: "Authentication required. Please check your API key.",
  INVALID_ACCESS_TOKEN: "Invalid API key. Please verify your credentials.",
  ERR_AUTH: "Authentication failed. Please check your API key.",
  NO_BALANCE: "Insufficient balance. Please top up your account.",
  INSUFFICIENT_BALANCE: "Insufficient balance. Please top up your account.",
  DUPLICATED_REF: "This order was already processed.",
  ERR_VALIDATION: "Please check your input and try again.",
  ERR_PHONE: "Invalid phone number. Please verify and try again.",
  ERR_STOCK: "This product is currently out of stock.",
  NOT_FOUND: "The requested resource was not found.",
  RATE_LIMIT_EXCEEDED: "Too many requests. Please wait a moment and try again.",
  ERR_SERVICE: "Service temporarily unavailable. Please try again.",
  INTERNAL_SERVER_ERROR: "An error occurred. Our team has been notified.",
  INTERNAL_ERROR: "An error occurred. Our team has been notified.",
};

function getUserFriendlyMessage(errorCode, defaultMessage) {
  return (
    errorMessages[errorCode] ||
    defaultMessage ||
    "An unexpected error occurred."
  );
}

// Usage
if (!data.success) {
  const userMessage = getUserFriendlyMessage(
    data.error.code,
    data.error.message
  );
  showErrorToUser(userMessage);
}
```

### 5. Enregistrez les Request IDs

```javascript theme={null}
function logError(error, context) {
  const errorLog = {
    timestamp: new Date().toISOString(),
    requestId: error.requestId,
    code: error.code,
    message: error.message,
    context: {
      userId: context.userId,
      operation: context.operation,
      // Don't log sensitive data
    },
  };

  // Log to your logging service
  logger.error("API Error", errorLog);

  // Alert on critical errors
  if (error.status >= 500) {
    alertOps("API Error", errorLog);
  }
}
```

## Patterns Avancés

### Pattern Circuit Breaker

Évitez les défaillances en cascade en interrompant les requêtes lorsque le taux d'erreur est élevé :

```javascript theme={null}
class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.failureCount = 0;
    this.threshold = threshold;
    this.timeout = timeout;
    this.state = "CLOSED"; // CLOSED, OPEN, HALF_OPEN
    this.nextAttempt = Date.now();
  }

  async execute(fn) {
    if (this.state === "OPEN") {
      if (Date.now() < this.nextAttempt) {
        throw new Error("Circuit breaker is OPEN");
      }
      this.state = "HALF_OPEN";
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.failureCount = 0;
    this.state = "CLOSED";
  }

  onFailure() {
    this.failureCount++;
    if (this.failureCount >= this.threshold) {
      this.state = "OPEN";
      this.nextAttempt = Date.now() + this.timeout;
    }
  }
}

// Usage
const breaker = new CircuitBreaker(5, 60000);

try {
  const result = await breaker.execute(() => sendTopup(data));
} catch (error) {
  if (error.message === "Circuit breaker is OPEN") {
    showMaintenanceMessage();
  }
}
```

### Surveillance des erreurs

Suivez les patterns d'erreurs pour identifier les problèmes tôt :

```javascript theme={null}
class ApiClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.errorStats = new Map();
  }

  trackError(errorCode) {
    const count = this.errorStats.get(errorCode) || 0;
    this.errorStats.set(errorCode, count + 1);
  }

  async request(url, options) {
    try {
      const response = await fetch(url, {
        ...options,
        headers: { ...options.headers, "X-Access-Token": this.apiKey },
      });

      const data = await response.json();

      if (!data.success) {
        this.trackError(data.error.code);

        if (this.errorStats.get(data.error.code) > 10) {
          this.sendAlert(`High error rate: ${data.error.code}`);
        }
      }

      return data;
    } catch (error) {
      this.trackError("NETWORK_ERROR");
      throw error;
    }
  }

  sendAlert(message) {
    console.error("ALERT:", message);
  }
}
```

### Gestion de UNKNOWN\_ERROR

<Warning>
  **Important :** Ne remboursez jamais immédiatement lors d'un UNKNOWN\_ERROR. Attendez toujours 24 heures pour la résolution.
</Warning>

```javascript theme={null}
async function handleUnknownError(orderId, topupId) {
  await db.orders.update({
    where: { id: orderId },
    data: {
      status: "REVIEW_NEEDED",
      reviewReason: "UNKNOWN_ERROR from API",
      reviewScheduled: new Date(Date.now() + 24 * 60 * 60 * 1000),
    },
  });

  await notifyUser(orderId, {
    title: "Order Under Review",
    message: "Your order is being verified. Status will update within 24 hours.",
  });
}
```

## Test des scénarios d'erreur

Utilisez le mode sandbox pour tester la gestion des erreurs :

```javascript theme={null}
// Test insufficient balance
// Set your sandbox balance very low

// Test validation errors
await sendTopUp({
  plan_code: 'INVALID_PLAN',
  MSSIDN: '123456789',     // Invalid format
  amount: -100             // Negative amount
});

// Test duplicate reference
const ref = 'test-duplicate-123';
await sendTopUp({ ref, ... });  // First request
await sendTopUp({ ref, ... });  // Should fail with DUPLICATED_REF
```

## Référence rapide

<CardGroup cols={2}>
  <Card title="Valider tôt" icon="circle-check">
    Valider les entrées côté client avant les appels API
  </Card>

  <Card title="Réessayer intelligemment" icon="arrows-rotate">
    Utiliser le backoff exponentiel pour les erreurs 5xx, jamais pour les 4xx
  </Card>

  <Card title="Journaliser le contexte" icon="file-lines">
    Toujours inclure requestId et contexte dans les logs
  </Card>

  <Card title="Retour utilisateur" icon="message">
    Afficher des messages d'erreur clairs et actionnables aux utilisateurs
  </Card>
</CardGroup>

## Étapes suivantes

<CardGroup cols={2}>
  <Card title="Endpoints principaux" icon="code" href="/fr/api-reference/core/health-check">
    Explorer tous les endpoints
  </Card>

  <Card title="Format de réponse" icon="file-code" href="/fr/api-reference/response-format">
    Comprendre les réponses API
  </Card>
</CardGroup>
