Aller au contenu principal
Zenovay
Pro Plan15 minutesIntermédiaire

Gestion des erreurs de l'API

Gérez gracieusement les erreurs de l'API Zenovay — codes d'erreur, stratégies de réessai et techniques de débogage. Apprenez la gestion des erreurs dans ce guide sur les intégrations d'API.

apierrorsdebuggingtroubleshootingbest-practices
Dernière mise à jour :

Apprenez à gérer gracieusement les erreurs de l'API Zenovay avec les codes d'erreur appropriés, les stratégies de réessai et les techniques de débogage.

L'API REST Zenovay est disponible sur les plans Pro, Scale et Enterprise. Les requêtes authentifiées avec une clé de plan gratuit sont rejetées avec un code 403 et API_PAID_PLAN_REQUIRED.

Format de réponse d'erreur

Chaque réponse API est enveloppée dans un indicateur success. Les erreurs renvoient success: false avec un objet error :

{
  "success": false,
  "error": {
    "message": "Rate limit exceeded (30 requests/minute). Try again in 42 seconds",
    "code": "RATE_LIMIT_EXCEEDED",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

Le code de statut HTTP se trouve dans la réponse elle-même (par exemple 429), pas dans le corps.

Champs de réponse

ChampDescription
successfalse en cas d'erreur, true en cas de succès
error.codeCode d'erreur lisible par machine (majuscules, par exemple RATE_LIMIT_EXCEEDED)
error.messageDescription d'erreur lisible par l'homme
error.timestampHorodatage ISO 8601 de la génération de l'erreur

Chaque réponse comporte également un en-tête x-request-id (un UUID). Incluez-le quand vous contactez le support – cela nous permet de tracer une seule requête de bout en bout.

Codes de statut HTTP

Codes de succès (2xx)

CodeDescription
200Requête réussie

Les réponses réussies utilisent le wrapper success: true :

{
  "success": true,
  "data": { /* ... */ },
  "timestamp": "2026-06-13T10:30:00.000Z"
}

Erreurs client (4xx)

CodeCode d'erreurDescription
400VALIDATION_ERROR / MISSING_SITE_ID / GENERIC_ERRORParamètre invalide ou manquant
401UNAUTHORIZEDClé API invalide ou manquante
403FORBIDDENLa clé API n'a pas accès à la ressource demandée
403API_PAID_PLAN_REQUIREDL'API nécessite un plan payant (les clés gratuites sont bloquées)
404NOT_FOUNDRessource non trouvée
409CONFLICTConflit de ressource (doublon)
429RATE_LIMIT_EXCEEDEDTrop de requêtes par minute
429MONTHLY_LIMIT_EXCEEDEDQuota de requêtes mensuelles atteint

Erreurs serveur (5xx)

CodeCode d'erreurDescription
500INTERNAL_ERROR / GENERIC_ERRORErreur serveur

Codes d'erreur courants

Erreurs d'authentification

Une clé manquante, une clé mal formée (les clés Zenovay commencent par zv_) ou une clé révoquée renvoient toutes 401 avec le code UNAUTHORIZED. Le message indique lequel :

// Clé API manquante
{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Missing API key. Use Authorization: Bearer <key> or X-API-Key header",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

// Clé invalide ou révoquée
{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid API key",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

Authentifiez-vous avec l'un ou l'autre en-tête :

Authorization: Bearer zv_YOUR_API_KEY
X-API-Key: zv_YOUR_API_KEY

Erreurs de validation

Les paramètres manquants ou mal formés renvoient une 400 avec un message en texte clair décrivant le problème :

// Paramètre de requête requis manquant
{
  "success": false,
  "error": {
    "code": "MISSING_SITE_ID",
    "message": "site_id parameter is required",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

// Entrée invalide
{
  "success": false,
  "error": {
    "code": "GENERIC_ERROR",
    "message": "Query must be 500 characters or less",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

Erreurs de limite de débit

Quand vous dépassez la limite de débit par minute, vous obtenez une 429 avec RATE_LIMIT_EXCEEDED. Lisez l'en-tête de réponse Retry-After (secondes) pour savoir combien de temps attendre :

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded (30 requests/minute). Try again in 45 seconds",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

La réponse inclut ces en-têtes :

En-têteDescription
Retry-AfterSecondes à attendre avant de réessayer
X-RateLimit-LimitVotre limite de requête par minute
X-RateLimit-ResetHorodatage ISO 8601 quand la fenêtre se réinitialise

L'épuisement du quota mensuel renvoie 429 avec MONTHLY_LIMIT_EXCEEDED et les en-têtes X-Usage-* (X-Usage-Monthly, X-Usage-Limit, X-Usage-Reset).

Erreurs de ressource

Demander un site web auquel vous n'avez pas accès ou qui n'existe pas renvoie 404 NOT_FOUND ou 403 FORBIDDEN :

// Site web non trouvé
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Website not found",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

// La clé n'a pas accès à ce site web
{
  "success": false,
  "error": {
    "code": "FORBIDDEN",
    "message": "API key does not have access to this website",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

Erreurs de limite de plan

Certains points de terminaison nécessitent un plan plus élevé que le vôtre. Les clés gratuites sont complètement bloquées ; les clés Pro sont bloquées des points de terminaison réservés à Scale (par exemple l'API de requête en langage naturel) :

// Clé gratuite appelant n'importe quel point de terminaison d'API
{
  "success": false,
  "error": {
    "code": "API_PAID_PLAN_REQUIRED",
    "message": "The Zenovay API requires a paid plan. Upgrade to Pro or higher to use API keys.",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

// Clé Pro appelant un point de terminaison réservé à Scale
{
  "success": false,
  "error": {
    "code": "FORBIDDEN",
    "message": "This endpoint requires a Scale plan or higher. Your current plan: Pro",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

Stratégies de gestion des erreurs

Gestion de base des erreurs

async function callZenovayAPI(endpoint) {
  try {
    const response = await fetch(`https://api.zenovay.com/api/external/v1${endpoint}`, {
      headers: {
        'X-API-Key': API_KEY,
      }
    });

    const body = await response.json();

    if (!response.ok || body.success === false) {
      throw new ZenovayAPIError(body.error, response.status, response.headers.get('x-request-id'));
    }

    return body.data;
  } catch (error) {
    if (error instanceof ZenovayAPIError) {
      handleAPIError(error);
    } else {
      // Erreur réseau
      console.error('Network error:', error);
    }
    throw error;
  }
}

class ZenovayAPIError extends Error {
  constructor(error, status, requestId) {
    super(error?.message);
    this.code = error?.code;
    this.status = status;
    this.requestId = requestId;
  }
}

function handleAPIError(error) {
  switch (error.code) {
    case 'UNAUTHORIZED':
      console.error('Invalid or missing API key');
      break;
    case 'API_PAID_PLAN_REQUIRED':
      console.error('This API requires a paid plan');
      break;
    case 'RATE_LIMIT_EXCEEDED':
      console.error('Rate limited - back off and retry');
      break;
    default:
      console.error(`API error: ${error.message}`);
  }
}

Réessai avec backoff exponentiel

Réessayez les réponses 429 et 5xx. Pour 429, respectez l'en-tête Retry-After plutôt que de deviner :

async function fetchWithRetry(url, options, maxRetries = 3) {
  const retryableStatus = [429, 500, 502, 503, 504];

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      const body = await response.json();

      if (response.ok && body.success !== false) {
        return body.data;
      }

      // Ne pas réessayer les erreurs client non-réessayables
      if (!retryableStatus.includes(response.status) || attempt === maxRetries) {
        throw new ZenovayAPIError(body.error, response.status, response.headers.get('x-request-id'));
      }

      // Préférez le Retry-After du serveur (secondes) pour 429
      let delay;
      const retryAfter = response.headers.get('Retry-After');
      if (response.status === 429 && retryAfter) {
        delay = parseInt(retryAfter, 10) * 1000;
      } else {
        delay = Math.min(1000 * Math.pow(2, attempt), 30000); // Max 30s
      }

      console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
      await sleep(delay);

    } catch (error) {
      if (error instanceof ZenovayAPIError) {
        throw error;
      }

      // Erreur réseau - réessayer
      if (attempt === maxRetries) {
        throw error;
      }

      await sleep(1000 * Math.pow(2, attempt));
    }
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

class ZenovayAPIError extends Error {
  constructor(error, status, requestId) {
    super(error?.message);
    this.code = error?.code;
    this.status = status;
    this.requestId = requestId;
  }
}

Gestion du débit

Réglez vos requêtes en utilisant les en-têtes de limite de débit pour vous éloigner avant un 429 :

class RateLimitedClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.queue = [];
    this.processing = false;
    this.rateLimitReset = null;
  }

  async request(endpoint) {
    return new Promise((resolve, reject) => {
      this.queue.push({ endpoint, resolve, reject });
      this.processQueue();
    });
  }

  async processQueue() {
    if (this.processing || this.queue.length === 0) return;

    // Attendre si limité en débit
    if (this.rateLimitReset && Date.now() < this.rateLimitReset) {
      const waitTime = this.rateLimitReset - Date.now();
      setTimeout(() => this.processQueue(), waitTime);
      return;
    }

    this.processing = true;
    const { endpoint, resolve, reject } = this.queue.shift();

    try {
      const response = await fetch(`https://api.zenovay.com/api/external/v1${endpoint}`, {
        headers: {
          'X-API-Key': this.apiKey,
        }
      });

      // Vérifier les en-têtes de limite de débit
      const remaining = response.headers.get('X-RateLimit-Remaining');
      const reset = response.headers.get('X-RateLimit-Reset');

      if (remaining === '0' && reset) {
        this.rateLimitReset = new Date(reset).getTime();
      }

      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After');
        this.rateLimitReset = Date.now() + ((parseInt(retryAfter, 10) || 60) * 1000);

        // Remettre la requête en file
        this.queue.unshift({ endpoint, resolve, reject });
      } else {
        const body = await response.json();
        if (response.ok && body.success !== false) {
          resolve(body.data);
        } else {
          reject(body.error);
        }
      }

    } catch (error) {
      reject(error);
    } finally {
      this.processing = false;
      if (this.queue.length > 0) {
        setImmediate(() => this.processQueue());
      }
    }
  }
}

Modèle Circuit Breaker

class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 5;
    this.resetTimeout = options.resetTimeout || 30000;
    this.failures = 0;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
    this.nextAttempt = null;
  }

  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.failures = 0;
    this.state = 'CLOSED';
  }

  onFailure() {
    this.failures++;
    if (this.failures >= this.failureThreshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.resetTimeout;
    }
  }
}

// Utilisation
const breaker = new CircuitBreaker();

async function getAnalytics(websiteId) {
  return breaker.execute(() =>
    fetch(`https://api.zenovay.com/api/external/v1/analytics/${websiteId}?range=7d`, {
      headers: {
        'X-API-Key': API_KEY,
      }
    })
  );
}

Techniques de débogage

Activer le mode débogage

// Enregistrer toutes les requêtes d'API
const DEBUG = process.env.ZENOVAY_DEBUG === 'true';

async function apiRequest(endpoint) {
  if (DEBUG) {
    console.log(`[Zenovay] Request: ${endpoint}`);
  }

  const response = await fetch(`https://api.zenovay.com/api/external/v1${endpoint}`, {
    headers: { 'X-API-Key': API_KEY },
  });
  const result = await response.json();

  if (DEBUG) {
    console.log(`[Zenovay] Response: ${response.status} [${response.headers.get('x-request-id')}]`, JSON.stringify(result, null, 2));
  }

  return result;
}

Suivi du ID de requête

Enregistrez toujours l'en-tête de réponse x-request-id quand une requête échoue – le support peut l'utiliser pour rechercher votre requête exacte :

function handleError(error) {
  console.error(`API Error [${error.requestId}]:`, error.message);

  // Inclure dans les rapports d'erreur
  if (typeof Sentry !== 'undefined') {
    Sentry.captureException(error, {
      extra: {
        requestId: error.requestId,
        code: error.code,
        status: error.status
      }
    });
  }
}

Valider avant d'envoyer

function validateQuery(params) {
  const errors = [];

  if (!params.site_id) {
    errors.push('site_id is required');
  }

  if (params.range && !['24h', '7d', '30d', '90d', '1y'].includes(params.range)) {
    errors.push("range must be one of 24h, 7d, 30d, 90d, 1y");
  }

  if (errors.length > 0) {
    throw new Error(`Validation failed: ${errors.join(', ')}`);
  }

  return true;
}

Exemples spécifiques au langage

Python

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

class ZenovayClient:
    def __init__(self, api_key):
        self.api_key = api_key
        self.base_url = 'https://api.zenovay.com/api/external/v1'

        # Configurer la stratégie de réessai
        self.session = requests.Session()
        retry = Retry(
            total=3,
            backoff_factor=1,
            status_forcelist=[429, 500, 502, 503, 504]
        )
        adapter = HTTPAdapter(max_retries=retry)
        self.session.mount('https://', adapter)

    def get_analytics(self, website_id, time_range='7d'):
        response = self.session.get(
            f'{self.base_url}/analytics/{website_id}',
            params={'range': time_range},
            headers={
                'X-API-Key': self.api_key,
            },
            timeout=10
        )

        body = response.json()
        if not response.ok or body.get('success') is False:
            error = body.get('error', {})
            raise ZenovayAPIError(
                code=error.get('code'),
                message=error.get('message'),
                status=response.status_code,
                request_id=response.headers.get('x-request-id')
            )
        return body['data']

class ZenovayAPIError(Exception):
    def __init__(self, code, message, status, request_id=None):
        self.code = code
        self.message = message
        self.status = status
        self.request_id = request_id
        super().__init__(f'[{code}] {message}')

Ruby

require 'net/http'
require 'json'

class ZenovayClient
  class APIError < StandardError
    attr_reader :code, :status, :request_id

    def initialize(code:, message:, status:, request_id: nil)
      @code = code
      @status = status
      @request_id = request_id
      super(message)
    end
  end

  def initialize(api_key)
    @api_key = api_key
    @base_url = 'https://api.zenovay.com/api/external/v1'
  end

  def get_analytics(website_id, time_range = '7d')
    request("/analytics/#{website_id}?range=#{time_range}")
  end

  private

  def request(endpoint, retries: 3)
    uri = URI("#{@base_url}#{endpoint}")
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true

    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = @api_key

    response = http.request(req)
    result = JSON.parse(response.body)

    if response.is_a?(Net::HTTPSuccess) && result['success'] != false
      result['data']
    else
      error = result['error'] || {}
      raise APIError.new(
        code: error['code'],
        message: error['message'],
        status: response.code.to_i,
        request_id: response['x-request-id']
      )
    end
  rescue APIError
    raise
  rescue StandardError => e
    retries -= 1
    retry if retries > 0
    raise e
  end
end

Surveillance des erreurs

Intégration avec le suivi des erreurs

// Intégration Sentry
import * as Sentry from '@sentry/node';

async function fetchAnalytics(websiteId) {
  try {
    return await apiRequest(`/analytics/${websiteId}`);
  } catch (error) {
    Sentry.captureException(error, {
      tags: {
        api_error_code: error.code,
        endpoint: '/analytics'
      },
      extra: {
        request_id: error.requestId,
        website_id: websiteId
      }
    });
    throw error;
  }
}

Alertes sur les erreurs critiques

function handleError(error) {
  // Alerte sur les problèmes d'authentification / plan
  if (error.code === 'UNAUTHORIZED' || error.code === 'API_PAID_PLAN_REQUIRED') {
    sendAlert('API authentication failed', error);
  }

  // Alerte sur l'épuisement du quota
  if (error.code === 'MONTHLY_LIMIT_EXCEEDED') {
    sendAlert('Monthly API quota reached', error);
  }

  // Enregistrer tous les erreurs
  console.error(`[${error.code}] ${error.message}`, {
    requestId: error.requestId,
    status: error.status
  });
}

Liste de contrôle de dépannage

La requête échoue

  1. Vérifiez que votre clé API est valide et commence par zv_
  2. Confirmez que votre plan inclut l'accès à l'API (Pro ou supérieur)
  3. Vérifiez l'URL du point de terminaison et le chemin de base (/api/external/v1)
  4. Validez les paramètres de requête (par exemple site_id, range)
  5. Vérifiez la connectivité réseau

Limité en débit

  1. Respectez l'en-tête Retry-After avant de réessayer
  2. Réglez les requêtes en utilisant les en-têtes X-RateLimit-*
  3. Implémentez un backoff exponentiel pour les réponses 5xx
  4. Envisagez une mise à niveau du plan si vous dépassez régulièrement la limite

Erreurs de validation

  1. Lisez la error.message – elle nomme le paramètre contrevenant
  2. Vérifiez que les paramètres requis sont présents
  3. Utilisez une valeur range supportée (24h, 7d, 30d, 90d, 1y)
  4. Conservez les chaînes de requête dans les limites de longueur documentées

Défaillances intermittentes

  1. Implémentez une logique de réessai avec backoff
  2. Utilisez le modèle circuit breaker
  3. Vérifiez les problèmes de réseau
  4. Surveillez la page de statut Zenovay

Étapes suivantes

Cet article vous a-t-il aidé ?