Free15 minutesintermediate

API Error Handling

Handle Zenovay API errors gracefully - error codes, retry strategies, and debugging techniques.

apierrorsdebuggingtroubleshootingbest-practices
Last updated: January 15, 2025

Learn how to handle Zenovay API errors gracefully with proper error codes, retry strategies, and debugging techniques.

Error Response Format

All API errors follow a consistent format:

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Please retry after 60 seconds.",
    "status": 429,
    "details": {
      "limit": 100,
      "remaining": 0,
      "reset_at": "2025-01-15T10:31:00Z"
    },
    "request_id": "req_abc123xyz"
  }
}

Response Fields

FieldDescription
codeMachine-readable error code
messageHuman-readable error description
statusHTTP status code
detailsAdditional context (when available)
request_idUnique request identifier for support

HTTP Status Codes

Success Codes (2xx)

CodeDescription
200Request successful
201Resource created
202Request accepted (async processing)
204Success, no content returned

Client Errors (4xx)

CodeError CodeDescription
400bad_requestInvalid request format
401unauthorizedInvalid or missing API key
403forbiddenInsufficient permissions
404not_foundResource not found
409conflictResource conflict (duplicate)
422validation_errorInvalid data provided
429rate_limit_exceededToo many requests

Server Errors (5xx)

CodeError CodeDescription
500internal_errorServer error
502bad_gatewayUpstream service error
503service_unavailableService temporarily unavailable
504gateway_timeoutRequest timeout

Common Error Codes

Authentication Errors

// Invalid API key
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid API key provided",
    "status": 401
  }
}

// Expired API key
{
  "error": {
    "code": "api_key_expired",
    "message": "API key has expired. Please generate a new key.",
    "status": 401
  }
}

// Missing authorization header
{
  "error": {
    "code": "missing_authorization",
    "message": "Authorization header is required",
    "status": 401
  }
}

Validation Errors

// Missing required field
{
  "error": {
    "code": "validation_error",
    "message": "Validation failed",
    "status": 422,
    "details": {
      "fields": {
        "website_id": ["Required field is missing"],
        "event": ["Required field is missing"]
      }
    }
  }
}

// Invalid field format
{
  "error": {
    "code": "validation_error",
    "message": "Validation failed",
    "status": 422,
    "details": {
      "fields": {
        "timestamp": ["Must be a valid ISO 8601 date"],
        "value": ["Must be a positive number"]
      }
    }
  }
}

Rate Limit Errors

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded",
    "status": 429,
    "details": {
      "limit": 100,
      "window": "1 minute",
      "remaining": 0,
      "reset_at": "2025-01-15T10:31:00Z",
      "retry_after": 45
    }
  }
}

Resource Errors

// Website not found
{
  "error": {
    "code": "website_not_found",
    "message": "Website with ID 'xyz123' not found",
    "status": 404
  }
}

// Duplicate order
{
  "error": {
    "code": "duplicate_resource",
    "message": "Order with ID 'ORD-123' already exists",
    "status": 409
  }
}

Permission Errors

{
  "error": {
    "code": "insufficient_permissions",
    "message": "API key does not have permission to access this resource",
    "status": 403
  }
}

Plan Limit Errors

{
  "error": {
    "code": "plan_limit_exceeded",
    "message": "Monthly event limit exceeded",
    "status": 403,
    "details": {
      "limit": 100000,
      "used": 100000,
      "plan": "pro",
      "reset_at": "2025-02-01T00:00:00Z"
    }
  }
}

Error Handling Strategies

Basic Error Handling

async function callZenovayAPI(endpoint, data) {
  try {
    const response = await fetch(`https://api.zenovay.com/api/external/v1${endpoint}`, {
      method: 'POST',
      headers: {
        'X-API-Key': API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    });

    if (!response.ok) {
      const error = await response.json();
      throw new ZenovayAPIError(error.error);
    }

    return await response.json();
  } catch (error) {
    if (error instanceof ZenovayAPIError) {
      handleAPIError(error);
    } else {
      // Network error
      console.error('Network error:', error);
    }
  }
}

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

function handleAPIError(error) {
  switch (error.code) {
    case 'unauthorized':
      console.error('Invalid API key');
      break;
    case 'rate_limit_exceeded':
      console.error(`Rate limited. Retry after ${error.details.retry_after}s`);
      break;
    case 'validation_error':
      console.error('Validation failed:', error.details.fields);
      break;
    default:
      console.error(`API error: ${error.message}`);
  }
}

Retry with Exponential Backoff

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

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

      if (response.ok) {
        return await response.json();
      }

      const error = await response.json();

      // Don't retry client errors (except rate limits)
      if (response.status < 500 && response.status !== 429) {
        throw new ZenovayAPIError(error.error);
      }

      // Check if we should retry
      if (!retryableStatus.includes(response.status) || attempt === maxRetries) {
        throw new ZenovayAPIError(error.error);
      }

      // Calculate delay
      let delay;
      if (response.status === 429 && error.error.details?.retry_after) {
        delay = error.error.details.retry_after * 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;
      }

      // Network error - retry
      if (attempt === maxRetries) {
        throw error;
      }

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

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

Rate Limit Handling

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

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

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

    // Wait if rate limited
    if (this.rateLimitReset && Date.now() < this.rateLimitReset) {
      const waitTime = this.rateLimitReset - Date.now();
      setTimeout(() => this.processQueue(), waitTime);
      return;
    }

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

    try {
      const response = await fetch(`https://api.zenovay.com/api/external/v1${endpoint}`, {
        method: 'POST',
        headers: {
          'X-API-Key': this.apiKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
      });

      // Check rate limit headers
      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 error = await response.json();
        this.rateLimitReset = Date.now() + (error.error.details.retry_after * 1000);

        // Re-queue the request
        this.queue.unshift({ endpoint, data, resolve, reject });
      } else if (response.ok) {
        resolve(await response.json());
      } else {
        reject(await response.json());
      }

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

Circuit Breaker Pattern

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

// Usage
const breaker = new CircuitBreaker();

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

Debugging Techniques

Enable Debug Mode

// Log all API requests
const DEBUG = process.env.ZENOVAY_DEBUG === 'true';

async function apiRequest(endpoint, data) {
  if (DEBUG) {
    console.log(`[Zenovay] Request: ${endpoint}`, JSON.stringify(data, null, 2));
  }

  const response = await fetch(/* ... */);
  const result = await response.json();

  if (DEBUG) {
    console.log(`[Zenovay] Response: ${response.status}`, JSON.stringify(result, null, 2));
  }

  return result;
}

Request ID Tracking

Always log the request_id from error responses:

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

  // Include in error reports
  if (typeof Sentry !== 'undefined') {
    Sentry.captureException(error, {
      extra: {
        requestId: error.requestId,
        code: error.code,
        details: error.details
      }
    });
  }
}

Validate Before Sending

function validateEventData(data) {
  const errors = [];

  if (!data.website_id) {
    errors.push('website_id is required');
  }

  if (!data.event) {
    errors.push('event name is required');
  }

  if (data.timestamp && isNaN(Date.parse(data.timestamp))) {
    errors.push('timestamp must be a valid ISO 8601 date');
  }

  if (data.properties) {
    for (const [key, value] of Object.entries(data.properties)) {
      if (typeof value === 'object' && value !== null) {
        errors.push(`property '${key}' must be a primitive value`);
      }
    }
  }

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

  return true;
}

Language-Specific Examples

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'

        # Configure retry strategy
        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'):
        try:
            response = self.session.get(
                f'{self.base_url}/analytics/{website_id}',
                params={'timeRange': time_range},
                headers={
                    'X-API-Key': self.api_key,
                },
                timeout=10
            )
            response.raise_for_status()
            return response.json()

        except requests.exceptions.HTTPError as e:
            error_data = e.response.json().get('error', {})
            raise ZenovayAPIError(
                code=error_data.get('code'),
                message=error_data.get('message'),
                status=e.response.status_code,
                request_id=error_data.get('request_id')
            )

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}?timeRange=#{time_range}")
  end

  private

  def request(endpoint, payload, 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
    req['Content-Type'] = 'application/json'
    req.body = payload.to_json

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

    if response.is_a?(Net::HTTPSuccess)
      result
    else
      error = result['error']
      raise APIError.new(
        code: error['code'],
        message: error['message'],
        status: response.code.to_i,
        request_id: error['request_id']
      )
    end
  rescue APIError
    raise
  rescue StandardError => e
    retries -= 1
    retry if retries > 0
    raise e
  end
end

Error Monitoring

Integrate with Error Tracking

// Sentry integration
import * as Sentry from '@sentry/node';

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

Alert on Critical Errors

function handleError(error) {
  // Alert on authentication issues
  if (error.code === 'unauthorized' || error.code === 'api_key_expired') {
    sendAlert('API authentication failed', error);
  }

  // Alert on plan limits
  if (error.code === 'plan_limit_exceeded') {
    sendAlert('Plan limit exceeded', error);
  }

  // Log all errors
  console.error(`[${error.code}] ${error.message}`, {
    requestId: error.requestId,
    details: error.details
  });
}

Troubleshooting Checklist

Request Fails

  1. Check API key is valid and not expired
  2. Verify endpoint URL is correct
  3. Ensure Content-Type header is application/json
  4. Validate request body format
  5. Check for network connectivity

Rate Limited

  1. Implement exponential backoff
  2. Batch requests when possible
  3. Use rate limit headers for pacing
  4. Consider upgrading plan if limits consistently hit

Validation Errors

  1. Check required fields are present
  2. Verify data types match specification
  3. Ensure timestamps are ISO 8601 format
  4. Validate property values are primitives

Intermittent Failures

  1. Implement retry logic with backoff
  2. Use circuit breaker pattern
  3. Check for network issues
  4. Monitor API status page

Next Steps

Was this article helpful?