Ir al contenido principal
Zenovay
Pro Plan15 minutesIntermedio

Manejo de errores de la API

Gestiona los errores de la API de Zenovay de forma elegante — códigos de error, estrategias de reintento y técnicas de depuración. Aprende sobre manejo de errores en esta guía de integraciones de API.

apierrorsdebuggingtroubleshootingbest-practices
Última actualización:

Aprende a gestionar los errores de la API de Zenovay de forma elegante con los códigos de error adecuados, estrategias de reintento y técnicas de depuración.

La API REST de Zenovay está disponible en los planes Pro, Scale y Enterprise. Las solicitudes autenticadas con una clave de plan gratuito se rechazan con un código 403 y API_PAID_PLAN_REQUIRED.

Formato de respuesta de error

Cada respuesta de API se envuelve en una bandera success. Los errores devuelven success: false con un objeto 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"
  }
}

El código de estado HTTP está en la respuesta misma (por ejemplo 429), no en el cuerpo.

Campos de respuesta

CampoDescripción
successfalse en caso de error, true en caso de éxito
error.codeCódigo de error legible por máquinas (mayúsculas, por ejemplo RATE_LIMIT_EXCEEDED)
error.messageDescripción del error legible por humanos
error.timestampMarca de tiempo ISO 8601 de cuando se generó el error

Cada respuesta también contiene un encabezado x-request-id (una UUID). Inclúyelo cuando contactes con soporte – nos permite rastrear una única solicitud de extremo a extremo.

Códigos de estado HTTP

Códigos de éxito (2xx)

CódigoDescripción
200Solicitud exitosa

Las respuestas exitosas utilizan el wrapper success: true:

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

Errores del cliente (4xx)

CódigoCódigo de errorDescripción
400VALIDATION_ERROR / MISSING_SITE_ID / GENERIC_ERRORParámetro inválido o faltante
401UNAUTHORIZEDClave API inválida o faltante
403FORBIDDENLa clave API no tiene acceso al recurso solicitado
403API_PAID_PLAN_REQUIREDLa API requiere un plan de pago (las claves gratuitas están bloqueadas)
404NOT_FOUNDRecurso no encontrado
409CONFLICTConflicto de recurso (duplicado)
429RATE_LIMIT_EXCEEDEDDemasiadas solicitudes este minuto
429MONTHLY_LIMIT_EXCEEDEDCuota de solicitudes mensual alcanzada

Errores del servidor (5xx)

CódigoCódigo de errorDescripción
500INTERNAL_ERROR / GENERIC_ERRORError del servidor

Códigos de error comunes

Errores de autenticación

Una clave faltante, una clave mal formada (las claves de Zenovay comienzan con zv_) o una clave revocada devuelven todas 401 con el código UNAUTHORIZED. El mensaje te dice cuál:

// Clave API faltante
{
  "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"
  }
}

// Clave inválida o revocada
{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid API key",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

Auténticate con cualquiera encabezado:

Authorization: Bearer zv_YOUR_API_KEY
X-API-Key: zv_YOUR_API_KEY

Errores de validación

Los parámetros faltantes o mal formados devuelven una 400 con un mensaje de texto plano que describe el problema:

// Parámetro de consulta requerido faltante
{
  "success": false,
  "error": {
    "code": "MISSING_SITE_ID",
    "message": "site_id parameter is required",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

// Entrada inválida
{
  "success": false,
  "error": {
    "code": "GENERIC_ERROR",
    "message": "Query must be 500 characters or less",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

Errores de límite de tasa

Cuando excedes el límite de tasa por minuto, obtienes una 429 con RATE_LIMIT_EXCEEDED. Lee el encabezado de respuesta Retry-After (segundos) para saber cuánto tiempo esperar:

{
  "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 respuesta incluye estos encabezados:

EncabezadoDescripción
Retry-AfterSegundos a esperar antes de reintentar
X-RateLimit-LimitTu límite de solicitud por minuto
X-RateLimit-ResetMarca de tiempo ISO 8601 cuando se reinicia la ventana

Agotar la cuota mensual devuelve 429 con MONTHLY_LIMIT_EXCEEDED y encabezados X-Usage-* (X-Usage-Monthly, X-Usage-Limit, X-Usage-Reset).

Errores de recurso

Solicitar un sitio web al que no tienes acceso o que no existe devuelve 404 NOT_FOUND o 403 FORBIDDEN:

// Sitio web no encontrado
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Website not found",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

// La clave no tiene acceso a este sitio web
{
  "success": false,
  "error": {
    "code": "FORBIDDEN",
    "message": "API key does not have access to this website",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

Errores de límite de plan

Algunos puntos de terminación requieren un plan superior al tuyo. Las claves gratuitas están completamente bloqueadas; las claves Pro están bloqueadas de los puntos de terminaison solo para Scale (por ejemplo, la API de consulta de lenguaje natural):

// Clave gratuita llamando a cualquier punto de terminaison de 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"
  }
}

// Clave Pro llamando a un punto de terminaison solo para 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"
  }
}

Estrategias de manejo de errores

Manejo básico de errores

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 {
      // Error de red
      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}`);
  }
}

Reintento con retroceso exponencial

Reintenta respuestas 429 y 5xx. Para 429, honra el encabezado Retry-After en lugar de adivinar:

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

      // No reintentar errores del cliente no-reintentables
      if (!retryableStatus.includes(response.status) || attempt === maxRetries) {
        throw new ZenovayAPIError(body.error, response.status, response.headers.get('x-request-id'));
      }

      // Prefiere el Retry-After del servidor (segundos) en 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); // Máx 30s
      }

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

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

      // Error de red - reintentar
      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;
  }
}

Manejo de límite de tasa

Ajusta tus solicitudes usando los encabezados de límite de tasa para retroceder antes de 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;

    // Espera si está limitado por tasa
    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,
        }
      });

      // Comprobar encabezados de límite de tasa
      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);

        // Volver a encolar la solicitud
        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());
      }
    }
  }
}

Patrón 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;
    }
  }
}

// Uso
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,
      }
    })
  );
}

Técnicas de depuración

Habilitar modo de depuración

// Registrar todas las solicitudes de la 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;
}

Seguimiento de ID de solicitud

Siempre registra el encabezado de respuesta x-request-id cuando una solicitud falla – el soporte puede utilizarlo para buscar tu solicitud exacta:

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

  // Incluir en informes de error
  if (typeof Sentry !== 'undefined') {
    Sentry.captureException(error, {
      extra: {
        requestId: error.requestId,
        code: error.code,
        status: error.status
      }
    });
  }
}

Validar antes de enviar

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

Ejemplos específicos del lenguaje

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'

        # Configurar estrategia de reintento
        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

Monitoreo de errores

Integración con rastreo de errores

// Integración de 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;
  }
}

Alertas en errores críticos

function handleError(error) {
  // Alerta sobre problemas de autenticación / plan
  if (error.code === 'UNAUTHORIZED' || error.code === 'API_PAID_PLAN_REQUIRED') {
    sendAlert('API authentication failed', error);
  }

  // Alerta sobre agotamiento de cuota
  if (error.code === 'MONTHLY_LIMIT_EXCEEDED') {
    sendAlert('Monthly API quota reached', error);
  }

  // Registrar todos los errores
  console.error(`[${error.code}] ${error.message}`, {
    requestId: error.requestId,
    status: error.status
  });
}

Lista de verificación de solución de problemas

La solicitud falla

  1. Verifica que tu clave API sea válida y comience con zv_
  2. Confirma que tu plan incluye acceso a la API (Pro o superior)
  3. Verifica la URL del punto de terminaison y la ruta base (/api/external/v1)
  4. Valida los parámetros de consulta (por ejemplo site_id, range)
  5. Comprueba la conectividad de red

Limitado por tasa

  1. Honra el encabezado Retry-After antes de reintentar
  2. Ajusta las solicitudes usando los encabezados X-RateLimit-*
  3. Implementa retroceso exponencial para respuestas 5xx
  4. Considera actualizar tu plan si constantemente excedes el límite

Errores de validación

  1. Lee la error.message – indica el parámetro contravención
  2. Verifica que los parámetros requeridos están presentes
  3. Usa un valor range soportado (24h, 7d, 30d, 90d, 1y)
  4. Mantén las cadenas de consulta dentro de los límites de longitud documentados

Fallos intermitentes

  1. Implementa lógica de reintento con retroceso
  2. Usa el patrón circuit breaker
  3. Comprueba problemas de red
  4. Monitorea la página de estado de Zenovay

Próximos pasos

¿Fue útil este artículo?