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
| Champ | Description |
|---|---|
success | false en cas d'erreur, true en cas de succès |
error.code | Code d'erreur lisible par machine (majuscules, par exemple RATE_LIMIT_EXCEEDED) |
error.message | Description d'erreur lisible par l'homme |
error.timestamp | Horodatage 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)
| Code | Description |
|---|---|
200 | Requê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)
| Code | Code d'erreur | Description |
|---|---|---|
400 | VALIDATION_ERROR / MISSING_SITE_ID / GENERIC_ERROR | Paramètre invalide ou manquant |
401 | UNAUTHORIZED | Clé API invalide ou manquante |
403 | FORBIDDEN | La clé API n'a pas accès à la ressource demandée |
403 | API_PAID_PLAN_REQUIRED | L'API nécessite un plan payant (les clés gratuites sont bloquées) |
404 | NOT_FOUND | Ressource non trouvée |
409 | CONFLICT | Conflit de ressource (doublon) |
429 | RATE_LIMIT_EXCEEDED | Trop de requêtes par minute |
429 | MONTHLY_LIMIT_EXCEEDED | Quota de requêtes mensuelles atteint |
Erreurs serveur (5xx)
| Code | Code d'erreur | Description |
|---|---|---|
500 | INTERNAL_ERROR / GENERIC_ERROR | Erreur 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ête | Description |
|---|---|
Retry-After | Secondes à attendre avant de réessayer |
X-RateLimit-Limit | Votre limite de requête par minute |
X-RateLimit-Reset | Horodatage 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
- Vérifiez que votre clé API est valide et commence par
zv_ - Confirmez que votre plan inclut l'accès à l'API (Pro ou supérieur)
- Vérifiez l'URL du point de terminaison et le chemin de base (
/api/external/v1) - Validez les paramètres de requête (par exemple
site_id,range) - Vérifiez la connectivité réseau
Limité en débit
- Respectez l'en-tête
Retry-Afteravant de réessayer - Réglez les requêtes en utilisant les en-têtes
X-RateLimit-* - Implémentez un backoff exponentiel pour les réponses
5xx - Envisagez une mise à niveau du plan si vous dépassez régulièrement la limite
Erreurs de validation
- Lisez la
error.message– elle nomme le paramètre contrevenant - Vérifiez que les paramètres requis sont présents
- Utilisez une valeur
rangesupportée (24h,7d,30d,90d,1y) - Conservez les chaînes de requête dans les limites de longueur documentées
Défaillances intermittentes
- Implémentez une logique de réessai avec backoff
- Utilisez le modèle circuit breaker
- Vérifiez les problèmes de réseau
- Surveillez la page de statut Zenovay