Passer au contenu principal

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.

Structure de la Réponse d’Erreur

Toutes les erreurs suivent un format cohérent :
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "details": {
      // additional context
    }
  },
  "requestId": "req_abc123"
}

Codes de Statut HTTP

StatutCatégorieDescription
400Erreur ClientParamètres de requête invalides ou erreur de validation
401AuthentificationClé API invalide ou manquante
403AutorisationPermissions ou solde insuffisants
404Non TrouvéLa ressource n’existe pas
429Limite de TauxTrop de requêtes
500Erreur ServeurErreur interne du serveur
503IndisponibleService temporairement indisponible

Codes d’Erreur Courants

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
// ✅ 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

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
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
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

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
// 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
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

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

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
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');
}

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
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

Bonnes Pratiques d’Implémentation

1. Vérifiez Toujours le Champ success

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

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

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

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

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é :
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 :
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

Important : Ne remboursez jamais immédiatement lors d’un UNKNOWN_ERROR. Attendez toujours 24 heures pour la résolution.
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 :
// 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

Valider tôt

Valider les entrées côté client avant les appels API

Réessayer intelligemment

Utiliser le backoff exponentiel pour les erreurs 5xx, jamais pour les 4xx

Journaliser le contexte

Toujours inclure requestId et contexte dans les logs

Retour utilisateur

Afficher des messages d’erreur clairs et actionnables aux utilisateurs

Étapes suivantes

Endpoints principaux

Explorer tous les endpoints

Format de réponse

Comprendre les réponses API