Shopify Integration
Code Examples
Complete code examples and integration patterns for different programming languages
Code Examples
This section provides comprehensive code examples for integrating with the invoice management system API across different programming languages and common use cases.
Authentication Examples
cURL Examples
Basic Authentication
# Simple GET request with authentication
curl -X GET "https://api.example.com/api/integrations/users" \
  -H "X-Invois-Key: your-api-key-here" \
  -H "Content-Type: application/json"POST Request with Authentication
# Create a new user
curl -X POST "https://api.example.com/api/integrations/users" \
  -H "X-Invois-Key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John Doe",
    "email": "john@example.com",
    "role": "customer"
  }'PUT Request with Authentication
# Update an existing user
curl -X PUT "https://api.example.com/api/integrations/users/user-123" \
  -H "X-Invois-Key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John Smith",
    "email": "johnsmith@example.com"
  }'Handling Authentication Errors
# Test with invalid API key to see error response
curl -X GET "https://api.example.com/api/integrations/users" \
  -H "X-Invois-Key: invalid-key" \
  -H "Content-Type: application/json" \
  -w "\nHTTP Status: %{http_code}\n"
# Expected response:
# HTTP Status: 401
# {"error":"Unauthorized: Valid API key required"}JavaScript Authentication Examples
Basic Fetch with Authentication
// Simple authenticated request
async function getUsers() {
  try {
    const response = await fetch('https://api.example.com/api/integrations/users', {
      method: 'GET',
      headers: {
        'X-Invois-Key': 'your-api-key-here',
        'Content-Type': 'application/json'
      }
    });
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Authentication or request failed:', error);
    throw error;
  }
}Authentication with Error Handling
class AuthenticatedApiClient {
  constructor(apiKey, baseUrl = 'https://api.example.com') {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl;
  }
  async makeRequest(endpoint, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    
    const config = {
      ...options,
      headers: {
        'X-Invois-Key': this.apiKey,
        'Content-Type': 'application/json',
        ...options.headers
      }
    };
    try {
      const response = await fetch(url, config);
      
      // Handle authentication errors specifically
      if (response.status === 401) {
        const errorData = await response.json();
        throw new AuthenticationError(errorData.error || 'Authentication failed');
      }
      
      if (!response.ok) {
        const errorData = await response.json();
        throw new ApiError(response.status, errorData);
      }
      return await response.json();
    } catch (error) {
      if (error instanceof AuthenticationError) {
        console.error('Authentication failed. Please check your API key.');
        // Handle authentication failure (e.g., redirect to login, refresh token, etc.)
      }
      throw error;
    }
  }
  // Test authentication
  async testAuthentication() {
    try {
      await this.makeRequest('/api/integrations/users?per_page=1');
      console.log('Authentication successful');
      return true;
    } catch (error) {
      console.error('Authentication test failed:', error.message);
      return false;
    }
  }
}
class AuthenticationError extends Error {
  constructor(message) {
    super(message);
    this.name = 'AuthenticationError';
  }
}
class ApiError extends Error {
  constructor(status, data) {
    super(data.message || 'API request failed');
    this.name = 'ApiError';
    this.status = status;
    this.data = data;
  }
}
// Usage
const client = new AuthenticatedApiClient('your-api-key-here');
// Test authentication before making requests
if (await client.testAuthentication()) {
  const users = await client.makeRequest('/api/integrations/users');
  console.log('Users:', users);
}Environment Variable Configuration
// Using environment variables for API key (Node.js)
const API_KEY = process.env.INVOIS_API_KEY;
const BASE_URL = process.env.INVOIS_API_BASE_URL || 'https://api.example.com';
if (!API_KEY) {
  throw new Error('INVOIS_API_KEY environment variable is required');
}
const apiClient = new AuthenticatedApiClient(API_KEY, BASE_URL);
// For browser environments, you might use build-time environment variables
const API_KEY = import.meta.env.VITE_INVOIS_API_KEY; // Vite
// or
const API_KEY = process.env.REACT_APP_INVOIS_API_KEY; // Create React AppPython Authentication Examples
Basic Requests with Authentication
import requests
import os
from typing import Dict, Any, Optional
class AuthenticatedApiClient:
    def __init__(self, api_key: str, base_url: str = 'https://api.example.com'):
        self.api_key = api_key
        self.base_url = base_url.rstrip('/')
        self.session = requests.Session()
        self.session.headers.update({
            'X-Invois-Key': api_key,
            'Content-Type': 'application/json'
        })
    def make_request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
        url = f"{self.base_url}{endpoint}"
        
        try:
            response = self.session.request(method, url, **kwargs)
            
            # Handle authentication errors
            if response.status_code == 401:
                error_data = response.json()
                raise AuthenticationError(error_data.get('error', 'Authentication failed'))
            
            response.raise_for_status()
            return response.json()
            
        except requests.exceptions.HTTPError as e:
            if response.status_code == 401:
                raise AuthenticationError('Invalid API key')
            raise ApiError(response.status_code, response.json())
        except requests.exceptions.RequestException as e:
            raise ConnectionError(f"Request failed: {str(e)}")
    def test_authentication(self) -> bool:
        """Test if the API key is valid"""
        try:
            self.make_request('GET', '/api/integrations/users', params={'per_page': 1})
            print("Authentication successful")
            return True
        except AuthenticationError as e:
            print(f"Authentication failed: {e}")
            return False
        except Exception as e:
            print(f"Authentication test failed: {e}")
            return False
class AuthenticationError(Exception):
    pass
class ApiError(Exception):
    def __init__(self, status_code: int, error_data: Dict[str, Any]):
        self.status_code = status_code
        self.error_data = error_data
        super().__init__(error_data.get('message', 'API request failed'))
# Usage with environment variables
API_KEY = os.getenv('INVOIS_API_KEY')
BASE_URL = os.getenv('INVOIS_API_BASE_URL', 'https://api.example.com')
if not API_KEY:
    raise ValueError('INVOIS_API_KEY environment variable is required')
client = AuthenticatedApiClient(API_KEY, BASE_URL)
# Test authentication
if client.test_authentication():
    # Make authenticated requests
    users = client.make_request('GET', '/api/integrations/users')
    print(f"Found {users['meta']['total']} users")Advanced Authentication with Retry Logic
import time
import random
from functools import wraps
def retry_on_auth_failure(max_retries: int = 3, backoff_factor: float = 1.0):
    """Decorator to retry requests on authentication failures"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None
            
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except AuthenticationError as e:
                    last_exception = e
                    if attempt < max_retries - 1:
                        # Add jitter to prevent thundering herd
                        delay = backoff_factor * (2 ** attempt) + random.uniform(0, 1)
                        print(f"Authentication failed, retrying in {delay:.2f}s...")
                        time.sleep(delay)
                    else:
                        print("Max authentication retries exceeded")
                        break
                except Exception as e:
                    # Don't retry on non-auth errors
                    raise e
            
            raise last_exception
        return wrapper
    return decorator
class RobustApiClient(AuthenticatedApiClient):
    @retry_on_auth_failure(max_retries=3)
    def make_authenticated_request(self, method: str, endpoint: str, **kwargs):
        return self.make_request(method, endpoint, **kwargs)
# Usage
client = RobustApiClient(API_KEY, BASE_URL)
users = client.make_authenticated_request('GET', '/api/integrations/users')PHP Authentication Examples
Basic cURL with Authentication
<?php
class AuthenticatedApiClient {
    private $apiKey;
    private $baseUrl;
    private $timeout;
    public function __construct($apiKey, $baseUrl = 'https://api.example.com', $timeout = 30) {
        $this->apiKey = $apiKey;
        $this->baseUrl = rtrim($baseUrl, '/');
        $this->timeout = $timeout;
    }
    public function makeRequest($method, $endpoint, $data = null) {
        $url = $this->baseUrl . $endpoint;
        
        $headers = [
            'X-Invois-Key: ' . $this->apiKey,
            'Content-Type: application/json'
        ];
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $this->timeout,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2
        ]);
        if ($data) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        }
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);
        if ($error) {
            throw new Exception("cURL error: $error");
        }
        $responseData = json_decode($response, true);
        // Handle authentication errors
        if ($httpCode === 401) {
            $errorMessage = $responseData['error'] ?? 'Authentication failed';
            throw new AuthenticationException($errorMessage);
        }
        if ($httpCode >= 400) {
            throw new ApiException($httpCode, $responseData);
        }
        return $responseData;
    }
    public function testAuthentication() {
        try {
            $this->makeRequest('GET', '/api/integrations/users?per_page=1');
            echo "Authentication successful\n";
            return true;
        } catch (AuthenticationException $e) {
            echo "Authentication failed: " . $e->getMessage() . "\n";
            return false;
        } catch (Exception $e) {
            echo "Authentication test failed: " . $e->getMessage() . "\n";
            return false;
        }
    }
}
class AuthenticationException extends Exception {}
class ApiException extends Exception {
    public $statusCode;
    public $errorData;
    public function __construct($statusCode, $errorData) {
        $this->statusCode = $statusCode;
        $this->errorData = $errorData;
        parent::__construct($errorData['message'] ?? 'API request failed');
    }
}
// Usage with environment variables
$apiKey = getenv('INVOIS_API_KEY');
$baseUrl = getenv('INVOIS_API_BASE_URL') ?: 'https://api.example.com';
if (!$apiKey) {
    throw new Exception('INVOIS_API_KEY environment variable is required');
}
$client = new AuthenticatedApiClient($apiKey, $baseUrl);
// Test authentication
if ($client->testAuthentication()) {
    try {
        $users = $client->makeRequest('GET', '/api/integrations/users');
        echo "Found {$users['meta']['total']} users\n";
    } catch (Exception $e) {
        echo "Error: " . $e->getMessage() . "\n";
    }
}
?>WordPress Plugin Authentication Example
<?php
// WordPress plugin authentication example
class InvoiceApiWordPressClient {
    private $apiKey;
    private $baseUrl;
    public function __construct() {
        $this->apiKey = get_option('invois_api_key');
        $this->baseUrl = get_option('invois_api_base_url', 'https://api.example.com');
        
        if (!$this->apiKey) {
            add_action('admin_notices', [$this, 'showApiKeyNotice']);
        }
    }
    public function makeRequest($method, $endpoint, $data = null) {
        if (!$this->apiKey) {
            throw new Exception('API key not configured');
        }
        $url = $this->baseUrl . $endpoint;
        
        $args = [
            'method' => $method,
            'headers' => [
                'X-Invois-Key' => $this->apiKey,
                'Content-Type' => 'application/json'
            ],
            'timeout' => 30
        ];
        if ($data) {
            $args['body'] = json_encode($data);
        }
        $response = wp_remote_request($url, $args);
        if (is_wp_error($response)) {
            throw new Exception('Request failed: ' . $response->get_error_message());
        }
        $statusCode = wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);
        $responseData = json_decode($body, true);
        // Handle authentication errors
        if ($statusCode === 401) {
            $errorMessage = $responseData['error'] ?? 'Authentication failed';
            throw new Exception("Authentication error: $errorMessage");
        }
        if ($statusCode >= 400) {
            $errorMessage = $responseData['message'] ?? 'API request failed';
            throw new Exception("API error ($statusCode): $errorMessage");
        }
        return $responseData;
    }
    public function showApiKeyNotice() {
        echo '<div class="notice notice-error"><p>Invoice API key is not configured. Please set it in the plugin settings.</p></div>';
    }
    public function testConnection() {
        try {
            $this->makeRequest('GET', '/api/integrations/users?per_page=1');
            return ['success' => true, 'message' => 'Connection successful'];
        } catch (Exception $e) {
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }
}
// Usage in WordPress
$apiClient = new InvoiceApiWordPressClient();
$connectionTest = $apiClient->testConnection();
if ($connectionTest['success']) {
    // Proceed with API calls
    $users = $apiClient->makeRequest('GET', '/api/integrations/users');
} else {
    // Handle connection failure
    error_log('Invoice API connection failed: ' . $connectionTest['message']);
}
?>Authentication Error Handling Examples
Comprehensive Error Handling (JavaScript)
class AuthenticationErrorHandler {
  static handleAuthError(error, apiClient) {
    if (error.status === 401) {
      console.error('Authentication failed:', error.message);
      
      // Log the error for debugging
      console.log('API Key used:', apiClient.apiKey ? 'Present' : 'Missing');
      console.log('Request headers:', error.requestHeaders);
      
      // Possible recovery actions:
      // 1. Check if API key is correctly configured
      if (!apiClient.apiKey || apiClient.apiKey.trim() === '') {
        throw new Error('API key is missing or empty. Please configure your API key.');
      }
      
      // 2. Check if API key format is correct
      if (apiClient.apiKey.length < 10) {
        throw new Error('API key appears to be invalid (too short). Please verify your API key.');
      }
      
      // 3. Suggest common fixes
      const suggestions = [
        'Verify your API key is correct',
        'Check for extra whitespace in the API key',
        'Ensure the API key hasn\'t been revoked',
        'Contact your administrator for a new API key'
      ];
      
      throw new Error(`Authentication failed. Try these solutions:\n${suggestions.map(s => `• ${s}`).join('\n')}`);
    }
    
    throw error; // Re-throw if not an auth error
  }
}
// Usage
try {
  const users = await apiClient.getUsers();
} catch (error) {
  try {
    AuthenticationErrorHandler.handleAuthError(error, apiClient);
  } catch (handledError) {
    // Display user-friendly error message
    console.error(handledError.message);
    // Show error to user in UI
    showErrorMessage(handledError.message);
  }
}Python Error Recovery
class AuthenticationErrorHandler:
    @staticmethod
    def handle_auth_error(error: AuthenticationError, client: AuthenticatedApiClient):
        print(f"Authentication failed: {error}")
        
        # Diagnostic information
        print(f"API Key configured: {'Yes' if client.api_key else 'No'}")
        print(f"Base URL: {client.base_url}")
        
        # Recovery suggestions
        suggestions = [
            "Verify your API key is correct",
            "Check for extra whitespace in the API key",
            "Ensure the API key hasn't been revoked",
            "Verify the base URL is correct",
            "Contact your administrator for a new API key"
        ]
        
        print("\nTroubleshooting suggestions:")
        for i, suggestion in enumerate(suggestions, 1):
            print(f"{i}. {suggestion}")
        
        # Attempt to validate API key format
        if not client.api_key:
            raise ValueError("API key is missing. Please set the INVOIS_API_KEY environment variable.")
        
        if len(client.api_key.strip()) < 10:
            raise ValueError("API key appears to be invalid (too short).")
        
        raise AuthenticationError("Authentication failed after validation checks.")
# Usage
try:
    users = client.make_request('GET', '/api/integrations/users')
except AuthenticationError as e:
    try:
        AuthenticationErrorHandler.handle_auth_error(e, client)
    except (ValueError, AuthenticationError) as handled_error:
        print(f"Error: {handled_error}")
        # Log error or notify userPHP Error Recovery
<?php
class AuthenticationErrorHandler {
    public static function handleAuthError($error, $client) {
        echo "Authentication failed: " . $error->getMessage() . "\n";
        
        // Diagnostic information
        echo "API Key configured: " . ($client->getApiKey() ? 'Yes' : 'No') . "\n";
        echo "Base URL: " . $client->getBaseUrl() . "\n";
        
        // Recovery suggestions
        $suggestions = [
            "Verify your API key is correct",
            "Check for extra whitespace in the API key",
            "Ensure the API key hasn't been revoked",
            "Verify the base URL is correct",
            "Contact your administrator for a new API key"
        ];
        
        echo "\nTroubleshooting suggestions:\n";
        foreach ($suggestions as $i => $suggestion) {
            echo ($i + 1) . ". $suggestion\n";
        }
        
        // Validate API key format
        $apiKey = $client->getApiKey();
        if (!$apiKey) {
            throw new Exception("API key is missing. Please configure it.");
        }
        
        if (strlen(trim($apiKey)) < 10) {
            throw new Exception("API key appears to be invalid (too short).");
        }
        
        throw new AuthenticationException("Authentication failed after validation checks.");
    }
}
// Usage
try {
    $users = $client->makeRequest('GET', '/api/integrations/users');
} catch (AuthenticationException $e) {
    try {
        AuthenticationErrorHandler::handleAuthError($e, $client);
    } catch (Exception $handledError) {
        echo "Error: " . $handledError->getMessage() . "\n";
        // Log error or notify user
    }
}
?>Complete Integration Examples
JavaScript/TypeScript SDK
interface ApiConfig {
  baseUrl: string;
  apiKey: string;
  timeout?: number;
}
interface PaginatedResponse<T> {
  data: T[];
  meta: {
    total: number;
    per_page: number;
    current_page: number;
    last_page: number;
    from: number;
    to: number;
  };
}
class InvoiceApiClient {
  private config: ApiConfig;
  private rateLimitInfo: {
    limit?: number;
    remaining?: number;
    reset?: Date;
  } = {};
  constructor(config: ApiConfig) {
    this.config = {
      timeout: 30000,
      ...config
    };
  }
  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const url = `${this.config.baseUrl}${endpoint}`;
    
    const response = await fetch(url, {
      ...options,
      headers: {
        'X-Invois-Key': this.config.apiKey,
        'Content-Type': 'application/json',
        ...options.headers
      },
      signal: AbortSignal.timeout(this.config.timeout!)
    });
    // Update rate limit info
    this.updateRateLimitInfo(response);
    if (!response.ok) {
      const errorData = await response.json();
      throw new ApiError(response.status, errorData);
    }
    return response.json();
  }
  private updateRateLimitInfo(response: Response) {
    this.rateLimitInfo = {
      limit: parseInt(response.headers.get('X-RateLimit-Limit') || '0'),
      remaining: parseInt(response.headers.get('X-RateLimit-Remaining') || '0'),
      reset: new Date(response.headers.get('X-RateLimit-Reset') || Date.now())
    };
  }
  // User Management
  async getUsers(params?: {
    query?: string;
    page?: number;
    per_page?: number;
  }): Promise<PaginatedResponse<User>> {
    const searchParams = new URLSearchParams();
    if (params?.query) searchParams.set('query', params.query);
    if (params?.page) searchParams.set('page', params.page.toString());
    if (params?.per_page) searchParams.set('per_page', params.per_page.toString());
    const endpoint = `/api/integrations/users${searchParams.toString() ? `?${searchParams}` : ''}`;
    return this.request<PaginatedResponse<User>>(endpoint);
  }
  async getUser(id: string): Promise<User> {
    return this.request<User>(`/api/integrations/users/${id}`);
  }
  async createUser(userData: CreateUserRequest): Promise<User> {
    return this.request<User>('/api/integrations/users', {
      method: 'POST',
      body: JSON.stringify(userData)
    });
  }
  async updateUser(id: string, userData: UpdateUserRequest): Promise<User> {
    return this.request<User>(`/api/integrations/users/${id}`, {
      method: 'PUT',
      body: JSON.stringify(userData)
    });
  }
  async getUserOrganizations(id: string): Promise<{ data: Organization[]; meta: { total: number; count: number } }> {
    return this.request(`/api/integrations/users/${id}/organizations`);
  }
  // Organization Management
  async getOrganizations(params?: {
    query?: string;
    page?: number;
    per_page?: number;
  }): Promise<PaginatedResponse<Organization>> {
    const searchParams = new URLSearchParams();
    if (params?.query) searchParams.set('query', params.query);
    if (params?.page) searchParams.set('page', params.page.toString());
    if (params?.per_page) searchParams.set('per_page', params.per_page.toString());
    const endpoint = `/api/integrations/organizations${searchParams.toString() ? `?${searchParams}` : ''}`;
    return this.request<PaginatedResponse<Organization>>(endpoint);
  }
  async getOrganization(id: string): Promise<Organization> {
    return this.request<Organization>(`/api/integrations/organizations/${id}`);
  }
  async updateOrganization(id: string, orgData: UpdateOrganizationRequest): Promise<Organization> {
    return this.request<Organization>(`/api/integrations/organizations/${id}`, {
      method: 'PUT',
      body: JSON.stringify(orgData)
    });
  }
  // Utility methods
  getRateLimitInfo() {
    return { ...this.rateLimitInfo };
  }
  isRateLimited(): boolean {
    return this.rateLimitInfo.remaining !== undefined && this.rateLimitInfo.remaining <= 0;
  }
}
class ApiError extends Error {
  constructor(
    public status: number,
    public data: any
  ) {
    super(data.message || 'API request failed');
    this.name = 'ApiError';
  }
}
// Usage Example
const client = new InvoiceApiClient({
  baseUrl: 'https://api.example.com',
  apiKey: 'your-api-key-here'
});
// Get users with search
const users = await client.getUsers({ query: 'john', page: 1, per_page: 20 });
console.log(`Found ${users.meta.total} users`);
// Create a new user
const newUser = await client.createUser({
  name: 'Jane Doe',
  email: 'jane@example.com',
  role: 'customer'
});
// Get user's organizations
const userOrgs = await client.getUserOrganizations(newUser.id);
console.log(`User belongs to ${userOrgs.meta.total} organizations`);Python SDK
import requests
import time
from typing import Dict, List, Optional, Any
from dataclasses import dataclass
from datetime import datetime
@dataclass
class RateLimitInfo:
    limit: Optional[int] = None
    remaining: Optional[int] = None
    reset: Optional[datetime] = None
class ApiError(Exception):
    def __init__(self, status_code: int, error_data: Dict[str, Any]):
        self.status_code = status_code
        self.error_data = error_data
        super().__init__(error_data.get('message', 'API request failed'))
class InvoiceApiClient:
    def __init__(self, base_url: str, api_key: str, timeout: int = 30):
        self.base_url = base_url.rstrip('/')
        self.api_key = api_key
        self.timeout = timeout
        self.rate_limit_info = RateLimitInfo()
        self.session = requests.Session()
        self.session.headers.update({
            'X-Invois-Key': api_key,
            'Content-Type': 'application/json'
        })
    def _update_rate_limit_info(self, response: requests.Response):
        self.rate_limit_info.limit = int(response.headers.get('X-RateLimit-Limit', 0))
        self.rate_limit_info.remaining = int(response.headers.get('X-RateLimit-Remaining', 0))
        reset_header = response.headers.get('X-RateLimit-Reset')
        if reset_header:
            self.rate_limit_info.reset = datetime.fromisoformat(reset_header.replace('Z', '+00:00'))
    def _request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
        url = f"{self.base_url}{endpoint}"
        
        try:
            response = self.session.request(method, url, timeout=self.timeout, **kwargs)
            self._update_rate_limit_info(response)
            
            if response.status_code == 429:
                # Handle rate limiting with retry
                error_data = response.json()
                retry_after = error_data.get('details', {}).get('retry_after', 60)
                print(f"Rate limited. Waiting {retry_after} seconds...")
                time.sleep(retry_after)
                return self._request(method, endpoint, **kwargs)
            
            response.raise_for_status()
            return response.json()
            
        except requests.exceptions.HTTPError:
            error_data = response.json()
            raise ApiError(response.status_code, error_data)
    # User Management
    def get_users(self, query: Optional[str] = None, page: int = 1, per_page: int = 10) -> Dict[str, Any]:
        params = {'page': page, 'per_page': per_page}
        if query:
            params['query'] = query
        
        return self._request('GET', '/api/integrations/users', params=params)
    def get_user(self, user_id: str) -> Dict[str, Any]:
        return self._request('GET', f'/api/integrations/users/{user_id}')
    def create_user(self, user_data: Dict[str, Any]) -> Dict[str, Any]:
        return self._request('POST', '/api/integrations/users', json=user_data)
    def update_user(self, user_id: str, user_data: Dict[str, Any]) -> Dict[str, Any]:
        return self._request('PUT', f'/api/integrations/users/{user_id}', json=user_data)
    def get_user_organizations(self, user_id: str) -> Dict[str, Any]:
        return self._request('GET', f'/api/integrations/users/{user_id}/organizations')
    # Organization Management
    def get_organizations(self, query: Optional[str] = None, page: int = 1, per_page: int = 10) -> Dict[str, Any]:
        params = {'page': page, 'per_page': per_page}
        if query:
            params['query'] = query
        
        return self._request('GET', '/api/integrations/organizations', params=params)
    def get_organization(self, org_id: str) -> Dict[str, Any]:
        return self._request('GET', f'/api/integrations/organizations/{org_id}')
    def update_organization(self, org_id: str, org_data: Dict[str, Any]) -> Dict[str, Any]:
        return self._request('PUT', f'/api/integrations/organizations/{org_id}', json=org_data)
    # Utility methods
    def get_rate_limit_info(self) -> RateLimitInfo:
        return self.rate_limit_info
    def is_rate_limited(self) -> bool:
        return self.rate_limit_info.remaining is not None and self.rate_limit_info.remaining <= 0
# Usage Example
client = InvoiceApiClient(
    base_url='https://api.example.com',
    api_key='your-api-key-here'
)
# Get users with search
users_response = client.get_users(query='john', page=1, per_page=20)
print(f"Found {users_response['meta']['total']} users")
# Create a new user
new_user = client.create_user({
    'name': 'Jane Doe',
    'email': 'jane@example.com',
    'role': 'customer'
})
# Get user's organizations
user_orgs = client.get_user_organizations(new_user['id'])
print(f"User belongs to {user_orgs['meta']['total']} organizations")
# Batch processing example
def sync_users_batch(client: InvoiceApiClient, batch_size: int = 50):
    page = 1
    while True:
        response = client.get_users(page=page, per_page=batch_size)
        users = response['data']
        
        if not users:
            break
            
        # Process users
        for user in users:
            print(f"Processing user: {user['name']}")
            # Your processing logic here
        
        # Check if we have more pages
        if page >= response['meta']['last_page']:
            break
            
        page += 1
        time.sleep(1)  # Rate limiting courtesy delay
# Run batch sync
sync_users_batch(client)PHP SDK
<?php
class InvoiceApiClient {
    private $baseUrl;
    private $apiKey;
    private $timeout;
    private $rateLimitInfo = [];
    public function __construct($baseUrl, $apiKey, $timeout = 30) {
        $this->baseUrl = rtrim($baseUrl, '/');
        $this->apiKey = $apiKey;
        $this->timeout = $timeout;
    }
    private function request($method, $endpoint, $data = null) {
        $url = $this->baseUrl . $endpoint;
        
        $headers = [
            'X-Invois-Key: ' . $this->apiKey,
            'Content-Type: application/json'
        ];
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $this->timeout,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_HEADERFUNCTION => [$this, 'handleHeaderLine']
        ]);
        if ($data) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        }
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);
        if ($error) {
            throw new Exception("cURL error: $error");
        }
        $responseData = json_decode($response, true);
        if ($httpCode >= 400) {
            if ($httpCode === 429) {
                $retryAfter = $responseData['details']['retry_after'] ?? 60;
                error_log("Rate limited. Waiting {$retryAfter} seconds...");
                sleep($retryAfter);
                return $this->request($method, $endpoint, $data);
            }
            
            throw new ApiException($httpCode, $responseData);
        }
        return $responseData;
    }
    private function handleHeaderLine($ch, $headerLine) {
        if (strpos($headerLine, 'X-RateLimit-') === 0) {
            $parts = explode(':', $headerLine, 2);
            if (count($parts) === 2) {
                $key = trim($parts[0]);
                $value = trim($parts[1]);
                $this->rateLimitInfo[$key] = $value;
            }
        }
        return strlen($headerLine);
    }
    // User Management
    public function getUsers($query = null, $page = 1, $perPage = 10) {
        $params = ['page' => $page, 'per_page' => $perPage];
        if ($query) {
            $params['query'] = $query;
        }
        
        $queryString = http_build_query($params);
        return $this->request('GET', "/api/integrations/users?$queryString");
    }
    public function getUser($userId) {
        return $this->request('GET', "/api/integrations/users/$userId");
    }
    public function createUser($userData) {
        return $this->request('POST', '/api/integrations/users', $userData);
    }
    public function updateUser($userId, $userData) {
        return $this->request('PUT', "/api/integrations/users/$userId", $userData);
    }
    public function getUserOrganizations($userId) {
        return $this->request('GET', "/api/integrations/users/$userId/organizations");
    }
    // Organization Management
    public function getOrganizations($query = null, $page = 1, $perPage = 10) {
        $params = ['page' => $page, 'per_page' => $perPage];
        if ($query) {
            $params['query'] = $query;
        }
        
        $queryString = http_build_query($params);
        return $this->request('GET', "/api/integrations/organizations?$queryString");
    }
    public function getOrganization($orgId) {
        return $this->request('GET', "/api/integrations/organizations/$orgId");
    }
    public function updateOrganization($orgId, $orgData) {
        return $this->request('PUT', "/api/integrations/organizations/$orgId", $orgData);
    }
    // Utility methods
    public function getRateLimitInfo() {
        return $this->rateLimitInfo;
    }
    public function isRateLimited() {
        $remaining = $this->rateLimitInfo['X-RateLimit-Remaining'] ?? null;
        return $remaining !== null && (int)$remaining <= 0;
    }
}
class ApiException extends Exception {
    public $statusCode;
    public $errorData;
    public function __construct($statusCode, $errorData) {
        $this->statusCode = $statusCode;
        $this->errorData = $errorData;
        parent::__construct($errorData['message'] ?? 'API request failed');
    }
}
// Usage Example
$client = new InvoiceApiClient(
    'https://api.example.com',
    'your-api-key-here'
);
try {
    // Get users with search
    $usersResponse = $client->getUsers('john', 1, 20);
    echo "Found {$usersResponse['meta']['total']} users\n";
    // Create a new user
    $newUser = $client->createUser([
        'name' => 'Jane Doe',
        'email' => 'jane@example.com',
        'role' => 'customer'
    ]);
    // Get user's organizations
    $userOrgs = $client->getUserOrganizations($newUser['id']);
    echo "User belongs to {$userOrgs['meta']['total']} organizations\n";
} catch (ApiException $e) {
    echo "API Error ({$e->statusCode}): {$e->getMessage()}\n";
    if (isset($e->errorData['details'])) {
        print_r($e->errorData['details']);
    }
} catch (Exception $e) {
    echo "Error: {$e->getMessage()}\n";
}
// Batch processing function
function syncUsersBatch($client, $batchSize = 50) {
    $page = 1;
    
    do {
        $response = $client->getUsers(null, $page, $batchSize);
        $users = $response['data'];
        
        foreach ($users as $user) {
            echo "Processing user: {$user['name']}\n";
            // Your processing logic here
        }
        
        $hasMore = $page < $response['meta']['last_page'];
        $page++;
        
        if ($hasMore) {
            sleep(1); // Rate limiting courtesy delay
        }
        
    } while ($hasMore);
}
// Run batch sync
syncUsersBatch($client);
?>Common Integration Patterns
User Synchronization
class UserSynchronizer {
  constructor(apiClient, localDatabase) {
    this.apiClient = apiClient;
    this.localDatabase = localDatabase;
  }
  async syncAllUsers() {
    let page = 1;
    let hasMore = true;
    let syncedCount = 0;
    while (hasMore) {
      try {
        const response = await this.apiClient.getUsers({ page, per_page: 100 });
        
        for (const user of response.data) {
          await this.syncUser(user);
          syncedCount++;
        }
        hasMore = page < response.meta.last_page;
        page++;
        console.log(`Synced ${syncedCount} users so far...`);
        
        // Rate limiting courtesy delay
        if (hasMore) {
          await new Promise(resolve => setTimeout(resolve, 1000));
        }
      } catch (error) {
        console.error('Sync error:', error);
        // Implement retry logic or break based on error type
        break;
      }
    }
    console.log(`Sync complete. Total users synced: ${syncedCount}`);
  }
  async syncUser(apiUser) {
    const localUser = await this.localDatabase.findUser(apiUser.id);
    
    if (!localUser) {
      // Create new user
      await this.localDatabase.createUser(apiUser);
      console.log(`Created user: ${apiUser.name}`);
    } else if (new Date(apiUser.updatedAt) > new Date(localUser.updatedAt)) {
      // Update existing user
      await this.localDatabase.updateUser(apiUser.id, apiUser);
      console.log(`Updated user: ${apiUser.name}`);
    }
  }
}Organization Management
class OrganizationManager {
  constructor(apiClient) {
    this.apiClient = apiClient;
  }
  async getOrganizationWithMembers(orgId) {
    const org = await this.apiClient.getOrganization(orgId);
    
    // Enrich with additional member details if needed
    const enrichedMembers = await Promise.all(
      org.members.map(async (member) => {
        const userDetails = await this.apiClient.getUser(member.userId);
        return {
          ...member,
          user: userDetails
        };
      })
    );
    return {
      ...org,
      members: enrichedMembers
    };
  }
  async findUserOrganizations(userId) {
    const userOrgs = await this.apiClient.getUserOrganizations(userId);
    
    // Get detailed organization info
    const detailedOrgs = await Promise.all(
      userOrgs.data.map(org => this.apiClient.getOrganization(org.id))
    );
    return detailedOrgs;
  }
  async updateOrganizationBatch(updates) {
    const results = [];
    
    for (const update of updates) {
      try {
        const result = await this.apiClient.updateOrganization(update.id, update.data);
        results.push({ success: true, id: update.id, data: result });
      } catch (error) {
        results.push({ success: false, id: update.id, error: error.message });
      }
      
      // Rate limiting delay
      await new Promise(resolve => setTimeout(resolve, 100));
    }
    return results;
  }
}Error Handling Patterns
Comprehensive Error Handler
class ApiErrorHandler {
  static async handleApiCall(apiCall, retryOptions = {}) {
    const { maxRetries = 3, baseDelay = 1000, maxDelay = 30000 } = retryOptions;
    
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      try {
        return await apiCall();
      } catch (error) {
        if (error instanceof ApiError) {
          switch (error.status) {
            case 400:
              // Validation errors - don't retry
              throw new Error(`Validation failed: ${JSON.stringify(error.data.details)}`);
            
            case 401:
              // Authentication error - don't retry
              throw new Error('Authentication failed. Check your API key.');
            
            case 404:
              // Not found - don't retry
              throw new Error('Resource not found.');
            
            case 409:
              // Conflict - don't retry
              throw new Error(`Conflict: ${error.data.message}`);
            
            case 429:
              // Rate limited - retry with backoff
              const retryAfter = error.data.details?.retry_after || Math.pow(2, attempt);
              const delay = Math.min(retryAfter * 1000, maxDelay);
              
              if (attempt < maxRetries - 1) {
                console.log(`Rate limited. Retrying in ${delay}ms...`);
                await new Promise(resolve => setTimeout(resolve, delay));
                continue;
              }
              break;
            
            case 500:
            case 502:
            case 503:
            case 504:
              // Server errors - retry with exponential backoff
              if (attempt < maxRetries - 1) {
                const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
                console.log(`Server error. Retrying in ${delay}ms...`);
                await new Promise(resolve => setTimeout(resolve, delay));
                continue;
              }
              break;
          }
        }
        
        // If we get here, either it's the last attempt or an unhandleable error
        throw error;
      }
    }
  }
}
// Usage
const result = await ApiErrorHandler.handleApiCall(
  () => client.createUser(userData),
  { maxRetries: 3, baseDelay: 1000 }
);Testing Examples
Unit Tests with Jest
import { InvoiceApiClient } from './invoice-api-client';
// Mock fetch for testing
global.fetch = jest.fn();
describe('InvoiceApiClient', () => {
  let client;
  
  beforeEach(() => {
    client = new InvoiceApiClient({
      baseUrl: 'https://api.test.com',
      apiKey: 'test-key'
    });
    
    fetch.mockClear();
  });
  test('should get users successfully', async () => {
    const mockResponse = {
      data: [{ id: 'user-1', name: 'John Doe' }],
      meta: { total: 1, page: 1 }
    };
    fetch.mockResolvedValueOnce({
      ok: true,
      json: async () => mockResponse,
      headers: new Map([
        ['X-RateLimit-Limit', '1000'],
        ['X-RateLimit-Remaining', '999']
      ])
    });
    const result = await client.getUsers();
    
    expect(fetch).toHaveBeenCalledWith(
      'https://api.test.com/api/integrations/users',
      expect.objectContaining({
        headers: expect.objectContaining({
          'X-Invois-Key': 'test-key'
        })
      })
    );
    
    expect(result).toEqual(mockResponse);
  });
  test('should handle API errors', async () => {
    const errorResponse = {
      error: 'Validation Error',
      message: 'Invalid data',
      details: { email: ['Email is required'] }
    };
    fetch.mockResolvedValueOnce({
      ok: false,
      status: 400,
      json: async () => errorResponse
    });
    await expect(client.createUser({})).rejects.toThrow('Invalid data');
  });
  test('should handle rate limiting', async () => {
    const rateLimitResponse = {
      error: 'Rate Limit Exceeded',
      message: 'Too many requests',
      details: { retry_after: 1 }
    };
    // First call returns rate limit error
    fetch.mockResolvedValueOnce({
      ok: false,
      status: 429,
      json: async () => rateLimitResponse
    });
    // Second call succeeds
    fetch.mockResolvedValueOnce({
      ok: true,
      json: async () => ({ data: [] }),
      headers: new Map()
    });
    // Should retry automatically
    const result = await client.getUsers();
    expect(fetch).toHaveBeenCalledTimes(2);
  });
});Next Steps
- Authentication - Set up API authentication
- Error Handling - Implement proper error handling
- Rate Limiting - Understand and handle rate limits
- Data Models - Review data structures and relationships