BizCARE MyInvois
Shopify Integration

Error Handling

Comprehensive guide to handling API errors and troubleshooting common issues

Error Handling

This guide covers all possible error scenarios, response formats, and troubleshooting steps for the invoice management system API.

Error Response Format

All API errors follow a consistent JSON format:

{
  "error": "Error Type",
  "message": "Human-readable error description",
  "details": {
    "field1": ["Specific error message"],
    "field2": ["Another error message"]
  }
}

HTTP Status Codes

400 Bad Request

Returned when the request contains invalid data or missing required fields.

{
  "error": "Validation Error",
  "message": "The request contains invalid data",
  "details": {
    "email": ["Email is required", "Email format is invalid"],
    "name": ["Name must be at least 2 characters"]
  }
}

Common Causes:

  • Missing required fields
  • Invalid data formats
  • Constraint violations
  • Invalid query parameters

401 Unauthorized

Returned when authentication fails or API key is invalid.

{
  "error": "Unauthorized",
  "message": "Invalid or missing API key"
}

Common Causes:

  • Missing X-Invois-Key header
  • Invalid API key
  • Expired API key
  • Incorrect header format

404 Not Found

Returned when the requested resource doesn't exist.

{
  "error": "Not Found",
  "message": "User not found"
}

Common Causes:

  • Invalid resource ID
  • Resource has been deleted
  • Incorrect endpoint URL
  • Typos in path parameters

409 Conflict

Returned when the request conflicts with existing data.

{
  "error": "Conflict",
  "message": "Email already exists",
  "details": {
    "email": ["This email is already registered"]
  }
}

Common Causes:

  • Duplicate email addresses
  • Duplicate organization slugs
  • Constraint violations
  • Race conditions

429 Too Many Requests

Returned when rate limits are exceeded.

{
  "error": "Rate Limit Exceeded",
  "message": "Too many requests. Please try again later.",
  "details": {
    "retry_after": 60,
    "limit": 1000,
    "remaining": 0,
    "reset_time": "2024-01-15T15:30:00Z"
  }
}

Response Headers:

  • X-RateLimit-Limit: Maximum requests allowed
  • X-RateLimit-Remaining: Requests remaining in current window
  • X-RateLimit-Reset: Time when limit resets
  • Retry-After: Seconds to wait before retrying

500 Internal Server Error

Returned when an unexpected server error occurs.

{
  "error": "Internal Server Error",
  "message": "An unexpected error occurred. Please try again later."
}

Error Handling Examples

JavaScript/TypeScript

async function handleApiRequest(url, options) {
  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'X-Invois-Key': 'your-api-key',
        'Content-Type': 'application/json',
        ...options.headers
      }
    });

    if (!response.ok) {
      const errorData = await response.json();
      
      switch (response.status) {
        case 400:
          console.error('Validation errors:', errorData.details);
          // Handle validation errors
          break;
        case 401:
          console.error('Authentication failed:', errorData.message);
          // Redirect to login or refresh API key
          break;
        case 404:
          console.error('Resource not found:', errorData.message);
          // Handle missing resource
          break;
        case 409:
          console.error('Conflict:', errorData.message);
          // Handle duplicate data
          break;
        case 429:
          console.error('Rate limit exceeded. Retry after:', errorData.details.retry_after);
          // Implement retry logic
          break;
        default:
          console.error('API error:', errorData.message);
      }
      
      throw new Error(errorData.message);
    }

    return await response.json();
  } catch (error) {
    console.error('Request failed:', error);
    throw error;
  }
}

Python

import requests
import time
from typing import Dict, Any

def handle_api_request(url: str, method: str = 'GET', data: Dict[Any, Any] = None) -> Dict[Any, Any]:
    headers = {
        'X-Invois-Key': 'your-api-key',
        'Content-Type': 'application/json'
    }
    
    try:
        response = requests.request(method, url, json=data, headers=headers)
        
        if response.status_code == 429:
            # Handle rate limiting with exponential backoff
            retry_after = response.json().get('details', {}).get('retry_after', 60)
            print(f"Rate limited. Waiting {retry_after} seconds...")
            time.sleep(retry_after)
            return handle_api_request(url, method, data)  # Retry
        
        response.raise_for_status()
        return response.json()
        
    except requests.exceptions.HTTPError as e:
        error_data = response.json()
        
        if response.status_code == 400:
            print("Validation errors:", error_data.get('details', {}))
        elif response.status_code == 401:
            print("Authentication failed:", error_data.get('message'))
        elif response.status_code == 404:
            print("Resource not found:", error_data.get('message'))
        elif response.status_code == 409:
            print("Conflict:", error_data.get('message'))
        
        raise Exception(error_data.get('message', 'API request failed'))
    
    except requests.exceptions.RequestException as e:
        print(f"Request failed: {e}")
        raise

PHP

<?php

function handleApiRequest($url, $method = 'GET', $data = null) {
    $headers = [
        'X-Invois-Key: your-api-key',
        'Content-Type: application/json'
    ];
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
    
    if ($data) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    }
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    $responseData = json_decode($response, true);
    
    if ($httpCode >= 400) {
        switch ($httpCode) {
            case 400:
                error_log('Validation errors: ' . print_r($responseData['details'], true));
                break;
            case 401:
                error_log('Authentication failed: ' . $responseData['message']);
                break;
            case 404:
                error_log('Resource not found: ' . $responseData['message']);
                break;
            case 409:
                error_log('Conflict: ' . $responseData['message']);
                break;
            case 429:
                $retryAfter = $responseData['details']['retry_after'] ?? 60;
                error_log("Rate limited. Waiting {$retryAfter} seconds...");
                sleep($retryAfter);
                return handleApiRequest($url, $method, $data); // Retry
        }
        
        throw new Exception($responseData['message'] ?? 'API request failed');
    }
    
    return $responseData;
}
?>

Retry Logic and Best Practices

Exponential Backoff

For rate limiting and temporary errors, implement exponential backoff:

async function retryWithBackoff(fn, maxRetries = 3, baseDelay = 1000) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
      
      const delay = baseDelay * Math.pow(2, attempt);
      console.log(`Attempt ${attempt + 1} failed. Retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Rate Limit Handling

class ApiClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.rateLimitReset = null;
  }
  
  async request(url, options = {}) {
    // Check if we're still rate limited
    if (this.rateLimitReset && Date.now() < this.rateLimitReset) {
      const waitTime = this.rateLimitReset - Date.now();
      console.log(`Waiting ${waitTime}ms for rate limit reset...`);
      await new Promise(resolve => setTimeout(resolve, waitTime));
    }
    
    const response = await fetch(url, {
      ...options,
      headers: {
        'X-Invois-Key': this.apiKey,
        'Content-Type': 'application/json',
        ...options.headers
      }
    });
    
    // Update rate limit info from headers
    const remaining = response.headers.get('X-RateLimit-Remaining');
    const reset = response.headers.get('X-RateLimit-Reset');
    
    if (reset) {
      this.rateLimitReset = new Date(reset).getTime();
    }
    
    if (response.status === 429) {
      const errorData = await response.json();
      const retryAfter = errorData.details?.retry_after * 1000 || 60000;
      this.rateLimitReset = Date.now() + retryAfter;
      throw new Error('Rate limit exceeded');
    }
    
    return response;
  }
}

Troubleshooting Common Issues

Authentication Problems

Issue: Getting 401 Unauthorized errors Solutions:

  1. Verify API key is correct and active
  2. Check that X-Invois-Key header is properly set
  3. Ensure API key has necessary permissions
  4. Contact administrator if key appears invalid

Validation Errors

Issue: Getting 400 Bad Request with validation details Solutions:

  1. Check all required fields are provided
  2. Verify data formats match API specifications
  3. Ensure string lengths meet requirements
  4. Validate enum values are from allowed sets

Rate Limiting

Issue: Getting 429 Too Many Requests errors Solutions:

  1. Implement exponential backoff retry logic
  2. Respect Retry-After header values
  3. Monitor rate limit headers to avoid limits
  4. Consider caching responses to reduce API calls

Resource Not Found

Issue: Getting 404 Not Found errors Solutions:

  1. Verify resource IDs are correct
  2. Check that resources haven't been deleted
  3. Ensure proper URL construction
  4. Validate path parameters

Monitoring and Logging

Request Logging

Log all API requests and responses for debugging:

function logApiCall(url, method, requestData, response, error) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    url,
    method,
    requestData,
    responseStatus: response?.status,
    responseData: response?.data,
    error: error?.message
  };
  
  console.log('API Call:', JSON.stringify(logEntry, null, 2));
}

Error Tracking

Implement error tracking to monitor API issues:

function trackApiError(error, context) {
  // Send to error tracking service (e.g., Sentry, Bugsnag)
  errorTracker.captureException(error, {
    tags: {
      component: 'api-client',
      endpoint: context.url
    },
    extra: context
  });
}

Next Steps