Pular para o conteúdo principal
Zenovay
Pro Plano15 minutesIntermediário

Tratamento de Erros da API

Trate erros da API do Zenovay de forma elegante — códigos de erro, estratégias de nova tentativa e técnicas de depuração. Saiba mais sobre tratamento de erros neste guia de integrações de API.

apierrorsdebuggingtroubleshootingbest-practices
Última atualização:

Aprenda a tratar erros da API do Zenovay de forma elegante com códigos de erro adequados, estratégias de retry e técnicas de depuração.

A API REST do Zenovay está disponível nos planos Pro, Scale e Enterprise. Requisições autenticadas com uma chave de plano gratuito são rejeitadas com um código 403 e API_PAID_PLAN_REQUIRED.

Formato de Resposta de Erro

Cada resposta de API é envolvida em uma flag success. Erros retornam success: false com um 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"
  }
}

O código de status HTTP está na resposta em si (por exemplo 429), não no corpo.

Campos da Resposta

CampoDescrição
successfalse em caso de erro, true em caso de sucesso
error.codeCódigo de erro legível por máquina (maiúsculas, por exemplo RATE_LIMIT_EXCEEDED)
error.messageDescrição do erro legível por humanos
error.timestampMarca de tempo ISO 8601 de quando o erro foi gerado

Cada resposta também inclui um cabeçalho x-request-id (um UUID). Inclua-o ao contactar o suporte – isso nos permite rastrear uma única requisição de ponta a ponta.

Códigos de Status HTTP

Códigos de Sucesso (2xx)

CódigoDescrição
200Requisição bem-sucedida

Respostas bem-sucedidas usam o wrapper success: true:

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

Erros do Cliente (4xx)

CódigoCódigo de ErroDescrição
400VALIDATION_ERROR / MISSING_SITE_ID / GENERIC_ERRORParâmetro inválido ou ausente
401UNAUTHORIZEDChave de API inválida ou ausente
403FORBIDDENChave de API não tem acesso ao recurso solicitado
403API_PAID_PLAN_REQUIREDA API requer um plano pago (chaves gratuitas são bloqueadas)
404NOT_FOUNDRecurso não encontrado
409CONFLICTConflito de recurso (duplicado)
429RATE_LIMIT_EXCEEDEDMuitas requisições este minuto
429MONTHLY_LIMIT_EXCEEDEDCota mensal de requisições alcançada

Erros do Servidor (5xx)

CódigoCódigo de ErroDescrição
500INTERNAL_ERROR / GENERIC_ERRORErro do servidor

Códigos de Erro Comuns

Erros de Autenticação

Uma chave ausente, uma chave mal formada (chaves do Zenovay começam com zv_) ou uma chave revogada retornam todas 401 com o código UNAUTHORIZED. A mensagem indica qual:

// Chave de API ausente
{
  "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"
  }
}

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

Autentique-se com qualquer um cabeçalho:

Authorization: Bearer zv_YOUR_API_KEY
X-API-Key: zv_YOUR_API_KEY

Erros de Validação

Parâmetros ausentes ou mal formados retornam uma 400 com uma mensagem de texto simples descrevendo o problema:

// Parâmetro de consulta obrigatório ausente
{
  "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"
  }
}

Erros de Limite de Taxa

Quando você excede o limite de taxa por minuto, obtém uma 429 com RATE_LIMIT_EXCEEDED. Leia o cabeçalho de resposta Retry-After (segundos) para saber quanto tempo 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"
  }
}

A resposta inclui estes cabeçalhos:

CabeçalhoDescrição
Retry-AfterSegundos a esperar antes de tentar novamente
X-RateLimit-LimitSeu limite de requisição por minuto
X-RateLimit-ResetMarca de tempo ISO 8601 quando a janela se reinicia

Exaurir a cota mensal retorna 429 com MONTHLY_LIMIT_EXCEEDED e cabeçalhos X-Usage-* (X-Usage-Monthly, X-Usage-Limit, X-Usage-Reset).

Erros de Recurso

Solicitar um site que você não tem acesso ou que não existe retorna 404 NOT_FOUND ou 403 FORBIDDEN:

// Site não encontrado
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Website not found",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

// A chave não tem acesso a este site
{
  "success": false,
  "error": {
    "code": "FORBIDDEN",
    "message": "API key does not have access to this website",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

Erros de Limite de Plano

Alguns endpoints requerem um plano superior ao seu. Chaves gratuitas são completamente bloqueadas; chaves Pro são bloqueadas de endpoints apenas para Scale (por exemplo, a API de consulta em linguagem natural):

// Chave gratuita chamando qualquer endpoint 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"
  }
}

// Chave Pro chamando um endpoint apenas 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"
  }
}

Estratégias de Tratamento de Erros

Tratamento Básico de Erros

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 {
      // Erro de rede
      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}`);
  }
}

Retry com Backoff Exponencial

Tente novamente respostas 429 e 5xx. Para 429, honre o cabeçalho Retry-After em vez de adivinhar:

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

      // Não tente novamente erros de cliente não-retriáveis
      if (!retryableStatus.includes(response.status) || attempt === maxRetries) {
        throw new ZenovayAPIError(body.error, response.status, response.headers.get('x-request-id'));
      }

      // Prefira o Retry-After do servidor (segundos) em 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;
      }

      // Erro de rede - tente novamente
      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;
  }
}

Tratamento de Limite de Taxa

Ajuste suas requisições usando os cabeçalhos de limite de taxa para se afastar antes de um 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;

    // Aguarde se estiver com limite de taxa
    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,
        }
      });

      // Verificar cabeçalhos de limite de taxa
      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);

        // Recolocar a requisição na fila
        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());
      }
    }
  }
}

Padrão 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 Depuração

Habilitar Modo Debug

// Registre todas as requisições da 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;
}

Rastreamento de ID de Requisição

Sempre registre o cabeçalho de resposta x-request-id quando uma requisição falhar – o suporte pode usá-lo para procurar sua requisição exata:

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

  // Incluir em relatórios de erro
  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;
}

Exemplos por Linguagem

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 estratégia de retry
        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

Monitoramento de Erros

Integrar com Rastreamento de Erros

// Integração 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 para Erros Críticos

function handleError(error) {
  // Alerte sobre problemas de autenticação / plano
  if (error.code === 'UNAUTHORIZED' || error.code === 'API_PAID_PLAN_REQUIRED') {
    sendAlert('API authentication failed', error);
  }

  // Alerte sobre esgotamento de cota
  if (error.code === 'MONTHLY_LIMIT_EXCEEDED') {
    sendAlert('Monthly API quota reached', error);
  }

  // Registre todos os erros
  console.error(`[${error.code}] ${error.message}`, {
    requestId: error.requestId,
    status: error.status
  });
}

Checklist de Solução de Problemas

Requisição Falha

  1. Verifique se sua chave de API é válida e começa com zv_
  2. Confirme que seu plano inclui acesso à API (Pro ou superior)
  3. Verifique a URL do endpoint e o caminho base (/api/external/v1)
  4. Valide os parâmetros de consulta (por exemplo site_id, range)
  5. Verifique a conectividade de rede

Limitado por Taxa

  1. Honre o cabeçalho Retry-After antes de tentar novamente
  2. Ajuste as requisições usando os cabeçalhos X-RateLimit-*
  3. Implemente backoff exponencial para respostas 5xx
  4. Considere fazer upgrade do seu plano se consistentemente exceder o limite

Erros de Validação

  1. Leia a error.message – ela nomeia o parâmetro ofensivo
  2. Verifique se os parâmetros obrigatórios estão presentes
  3. Use um valor range suportado (24h, 7d, 30d, 90d, 1y)
  4. Mantenha as strings de consulta dentro dos limites de comprimento documentados

Falhas Intermitentes

  1. Implemente lógica de retry com backoff
  2. Use o padrão circuit breaker
  3. Verifique problemas de rede
  4. Monitore a página de status do Zenovay

Próximos Passos

Este artigo foi útil?