Learn how to handle Zenovay API errors gracefully with proper error codes, retry strategies, and debugging techniques.
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
| Field | Description |
|---|
code | Machine-readable error code |
message | Human-readable error description |
status | HTTP status code |
details | Additional context (when available) |
request_id | Unique request identifier for support |
HTTP Status Codes
Success Codes (2xx)
| Code | Description |
|---|
200 | Request successful |
201 | Resource created |
202 | Request accepted (async processing) |
204 | Success, no content returned |
Client Errors (4xx)
| Code | Error Code | Description |
|---|
400 | bad_request | Invalid request format |
401 | unauthorized | Invalid or missing API key |
403 | forbidden | Insufficient permissions |
404 | not_found | Resource not found |
409 | conflict | Resource conflict (duplicate) |
422 | validation_error | Invalid data provided |
429 | rate_limit_exceeded | Too many requests |
Server Errors (5xx)
| Code | Error Code | Description |
|---|
500 | internal_error | Server error |
502 | bad_gateway | Upstream service error |
503 | service_unavailable | Service temporarily unavailable |
504 | gateway_timeout | Request timeout |
Common Error Codes
Authentication Errors
{
"error": {
"code": "unauthorized",
"message": "Invalid API key provided",
"status": 401
}
}
{
"error": {
"code": "api_key_expired",
"message": "API key has expired. Please generate a new key.",
"status": 401
}
}
{
"error": {
"code": "missing_authorization",
"message": "Authorization header is required",
"status": 401
}
}
Validation Errors
{
"error": {
"code": "validation_error",
"message": "Validation failed",
"status": 422,
"details": {
"fields": {
"website_id": ["Required field is missing"],
"event": ["Required field is missing"]
}
}
}
}
{
"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
{
"error": {
"code": "website_not_found",
"message": "Website with ID 'xyz123' not found",
"status": 404
}
}
{
"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 {
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();
if (response.status < 500 && response.status !== 429) {
throw new ZenovayAPIError(error.error);
}
if (!retryableStatus.includes(response.status) || attempt === maxRetries) {
throw new ZenovayAPIError(error.error);
}
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);
}
console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
await sleep(delay);
} catch (error) {
if (error instanceof ZenovayAPIError) {
throw error;
}
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;
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)
});
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);
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';
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;
}
}
}
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
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);
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'
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
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) {
if (error.code === 'unauthorized' || error.code === 'api_key_expired') {
sendAlert('API authentication failed', error);
}
if (error.code === 'plan_limit_exceeded') {
sendAlert('Plan limit exceeded', error);
}
console.error(`[${error.code}] ${error.message}`, {
requestId: error.requestId,
details: error.details
});
}
Troubleshooting Checklist
Request Fails
- Check API key is valid and not expired
- Verify endpoint URL is correct
- Ensure Content-Type header is
application/json
- Validate request body format
- Check for network connectivity
Rate Limited
- Implement exponential backoff
- Batch requests when possible
- Use rate limit headers for pacing
- Consider upgrading plan if limits consistently hit
Validation Errors
- Check required fields are present
- Verify data types match specification
- Ensure timestamps are ISO 8601 format
- Validate property values are primitives
Intermittent Failures
- Implement retry logic with backoff
- Use circuit breaker pattern
- Check for network issues
- Monitor API status page
Next Steps