# Error Handling & Troubleshooting

Comprehensive guide to handling VBAPI errors, implementing robust retry logic, and troubleshooting common integration issues.

## Overview

Understanding VBAPI's error patterns helps you build robust integrations with proper error handling and recovery strategies. This guide covers error categories, recovery patterns, and comprehensive troubleshooting techniques.

## Error Categories

### Authentication Errors (401, 403)

#### 401 Unauthorized

**Common Causes:**

- Expired or invalid ID token
- Missing Authorization header
- Invalid credentials during authentication


**Recovery Strategy:**

```python
def handle_auth_error(response, token_manager):
    """Handle authentication errors with automatic recovery"""
    if response.status_code == 401:
        logger.warning("Authentication failed, refreshing token...")
        token_manager.force_refresh()
        return True  # Indicates retry needed
    return False

def resilient_api_call(url, headers, data=None):
    """API call with automatic auth recovery"""
    max_retries = 2
    
    for attempt in range(max_retries):
        try:
            response = requests.post(url, headers=headers, json=data)
            
            if response.status_code == 401 and attempt < max_retries - 1:
                if handle_auth_error(response, token_manager):
                    headers['Authorization'] = f'Bearer {token_manager.get_valid_token()}'
                    continue  # Retry with new token
            
            response.raise_for_status()
            return response.json()
            
        except requests.exceptions.RequestException as e:
            logger.error(f"Request failed on attempt {attempt + 1}: {e}")
            if attempt == max_retries - 1:
                raise
```

#### 403 Forbidden

**Common Causes:**

- Missing User-Agent header
- Insufficient permissions for the requested resource
- Blocked IP address


**Solutions:**

```python
def validate_headers(headers):
    """Validate required headers before making requests"""
    required_headers = ['User-Agent', 'x-api-key', 'Authorization', 'vbasoftware-database']
    
    for header in required_headers:
        if header not in headers:
            raise ValueError(f"Missing required header: {header}")
    
    # Ensure User-Agent is descriptive
    user_agent = headers.get('User-Agent', '')
    if not user_agent or user_agent == 'python-requests':
        raise ValueError("User-Agent must be descriptive (e.g., 'MyCompany-Integration/1.0')")

# Always validate before API calls
headers = get_authenticated_headers()
validate_headers(headers)
response = requests.post(url, headers=headers, json=data)
```

### Rate Limiting Errors (429)

#### Understanding Rate Limits

- **25 requests/second** sustained rate
- **50 requests/second** burst rate
- Token bucket algorithm with smoothing


#### Exponential Backoff Implementation

```python
import time
import random

def exponential_backoff(attempt, max_attempts=5, base_delay=1, max_delay=60):
    """Implements exponential backoff with jitter"""
    if attempt >= max_attempts:
        raise Exception(f"Max retry attempts ({max_attempts}) reached")
    
    # Calculate delay: base_delay * (2 ^ attempt) + jitter
    delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
    delay = min(delay, max_delay)  # Cap at max_delay
    
    logger.info(f"Rate limited, waiting {delay:.2f} seconds before retry {attempt + 1}")
    time.sleep(delay)

def robust_api_call(url, headers, data=None, max_retries=5):
    """API call with intelligent retry logic"""
    for attempt in range(max_retries):
        try:
            response = requests.post(url, headers=headers, json=data, timeout=30)
            
            # Handle rate limiting
            if response.status_code == 429:
                logger.warning(f"Rate limited, attempt {attempt + 1}/{max_retries}")
                exponential_backoff(attempt)
                continue
            
            # Handle auth errors
            if response.status_code == 401:
                if handle_auth_error(response, token_manager):
                    headers['Authorization'] = f'Bearer {token_manager.get_valid_token()}'
                    continue
            
            response.raise_for_status()
            return response.json()
            
        except requests.exceptions.Timeout:
            logger.warning(f"Request timeout on attempt {attempt + 1}")
            if attempt < max_retries - 1:
                exponential_backoff(attempt)
                continue
            raise
            
        except requests.exceptions.ConnectionError:
            logger.warning(f"Connection error on attempt {attempt + 1}")
            if attempt < max_retries - 1:
                exponential_backoff(attempt)
                continue
            raise
    
    raise Exception("All retry attempts failed")
```

### Client Errors (4xx)

#### 400 Bad Request

**Common Causes:**

- Malformed JSON in request body
- Missing required fields
- Invalid data types or formats
- Constraint violations


**Debugging Strategy:**

```python
def parse_validation_errors(error_response):
    """Extract validation details from 400 responses"""
    if error_response.get('error', {}).get('status') == 400:
        detail = error_response['error'].get('detail', '')
        print(f"Validation error: {detail}")
        
        # Log the activity ID for support
        activity_id = error_response.get('debug', {}).get('activityId')
        print(f"Activity ID: {activity_id}")
        
        return activity_id
    return None
```

### Server Errors (5xx)

#### 500 Internal Server Error

**Causes:**

- Unexpected server-side issues
- Database connectivity problems
- Processing errors


**Response Strategy:**

```python
def handle_server_error(response):
    """Handle server errors appropriately"""
    activity_id = response.get('debug', {}).get('activityId', 'N/A')
    
    if response.status_code in [500, 502, 503, 504]:
        print(f"Server error {response.status_code}")
        print(f"Activity ID for support: {activity_id}")
        
        # Log for monitoring
        logger.error(f"VBAPI server error: {response.status_code}, Activity: {activity_id}")
        
        # Don't retry 500s immediately - may indicate a persistent issue
        if response.status_code == 500:
            return False  # Don't retry
        
        # Retry 502, 503, 504 (likely transient)
        return True  # Retry after delay
```

## Comprehensive Error Handler

```python
import logging
import requests
from collections import defaultdict

logger = logging.getLogger(__name__)

class VBAPIErrorHandler:
    def __init__(self):
        self.error_counts = defaultdict(int)
    
    def handle_response(self, response, endpoint):
        """Centralized error handling with monitoring"""
        
        # Track error rates
        status = response.status_code
        self.error_counts[status] += 1
        
        # Extract activity ID
        activity_id = None
        try:
            if hasattr(response, 'json'):
                activity_id = response.json().get('debug', {}).get('activityId')
        except:
            pass
        
        # Handle specific error types
        if status == 401:
            return self._handle_auth_error(response, activity_id)
        elif status == 403:
            return self._handle_forbidden(response, activity_id)
        elif status == 429:
            return self._handle_rate_limit(response, activity_id)
        elif 400 <= status < 500:
            return self._handle_client_error(response, activity_id, endpoint)
        elif 500 <= status < 600:
            return self._handle_server_error(response, activity_id, endpoint)
        
        return "success"
    
    def _handle_auth_error(self, response, activity_id):
        logger.warning(f"Authentication failed: {activity_id}")
        return "refresh_token"
    
    def _handle_forbidden(self, response, activity_id):
        logger.error(f"Forbidden access: {activity_id}")
        return "check_permissions"
    
    def _handle_rate_limit(self, response, activity_id):
        logger.warning(f"Rate limited: {activity_id}")
        return "retry_with_backoff"
    
    def _handle_client_error(self, response, activity_id, endpoint):
        logger.error(f"Client error {response.status_code} on {endpoint}: {activity_id}")
        return "client_error"
    
    def _handle_server_error(self, response, activity_id, endpoint):
        logger.error(f"Server error {response.status_code} on {endpoint}: {activity_id}")
        
        # Alert on server errors
        if response.status_code == 500:
            self._alert_support(activity_id, endpoint, response.status_code)
        
        return "retry_if_transient"
    
    def _alert_support(self, activity_id, endpoint, status_code):
        """Alert mechanism for serious errors"""
        alert_message = f"VBAPI Error: {status_code} on {endpoint}, Activity: {activity_id}"
        # Send to monitoring system, email, Slack, etc.
        print(f"ALERT: {alert_message}")
    
    def get_error_stats(self):
        """Get current error statistics"""
        return dict(self.error_counts)
```

## Circuit Breaker Pattern

```python
import time
from enum import Enum

class CircuitState(Enum):
    CLOSED = "closed"
    OPEN = "open"
    HALF_OPEN = "half_open"

class CircuitBreaker:
    def __init__(self, failure_threshold=5, timeout=60, expected_exception=Exception):
        self.failure_threshold = failure_threshold
        self.timeout = timeout
        self.expected_exception = expected_exception
        self.failure_count = 0
        self.last_failure_time = None
        self.state = CircuitState.CLOSED
    
    def call(self, func, *args, **kwargs):
        """Execute function with circuit breaker protection"""
        if self.state == CircuitState.OPEN:
            if time.time() - self.last_failure_time > self.timeout:
                self.state = CircuitState.HALF_OPEN
                self.failure_count = 0
            else:
                raise Exception("Circuit breaker is OPEN")
        
        try:
            result = func(*args, **kwargs)
            self._on_success()
            return result
        except self.expected_exception as e:
            self._on_failure()
            raise
    
    def _on_success(self):
        """Handle successful call"""
        self.failure_count = 0
        self.state = CircuitState.CLOSED
    
    def _on_failure(self):
        """Handle failed call"""
        self.failure_count += 1
        self.last_failure_time = time.time()
        
        if self.failure_count >= self.failure_threshold:
            self.state = CircuitState.OPEN

# Usage
api_circuit_breaker = CircuitBreaker(failure_threshold=3, timeout=30)

def protected_api_call(url, headers, data):
    """API call protected by circuit breaker"""
    return api_circuit_breaker.call(
        requests.post, 
        url, 
        headers=headers, 
        json=data,
        timeout=30
    )
```

## Activity ID Tracking

```python
import uuid
from contextvars import ContextVar

# Thread-local storage for request tracking
request_context = ContextVar('request_context', default={})

class RequestTracker:
    def __init__(self):
        self.request_id = str(uuid.uuid4())
        self.activity_ids = []
    
    def add_activity_id(self, activity_id):
        """Track activity ID from VBAPI response"""
        if activity_id:
            self.activity_ids.append(activity_id)
    
    def get_correlation_data(self):
        """Get all correlation data for debugging"""
        return {
            'request_id': self.request_id,
            'activity_ids': self.activity_ids
        }

def track_api_call(func):
    """Decorator to automatically track API calls"""
    def wrapper(*args, **kwargs):
        # Get or create request tracker
        ctx = request_context.get()
        if 'tracker' not in ctx:
            ctx['tracker'] = RequestTracker()
            request_context.set(ctx)
        
        try:
            response = func(*args, **kwargs)
            
            # Extract and track activity ID
            if hasattr(response, 'json'):
                try:
                    data = response.json()
                    activity_id = data.get('debug', {}).get('activityId')
                    ctx['tracker'].add_activity_id(activity_id)
                except:
                    pass
            
            return response
        except Exception as e:
            # Log correlation data on errors
            correlation = ctx['tracker'].get_correlation_data()
            logger.error(f"API call failed: {e}, Correlation: {correlation}")
            raise
    
    return wrapper

# Usage
@track_api_call
def make_api_request(url, headers, data):
    return requests.post(url, headers=headers, json=data)
```

## Monitoring & Alerting Setup

```python
import time
import threading
from collections import deque, defaultdict

class VBAPIMonitor:
    def __init__(self, alert_threshold=0.1):  # 10% error rate
        self.alert_threshold = alert_threshold
        self.requests = deque()
        self.errors = deque()
        self.lock = threading.Lock()
        self.start_monitoring()
    
    def record_request(self, success=True, error_type=None):
        """Record API request result"""
        now = time.time()
        
        with self.lock:
            self.requests.append(now)
            if not success:
                self.errors.append((now, error_type))
            
            # Clean old data (last hour)
            cutoff = now - 3600
            while self.requests and self.requests[0] < cutoff:
                self.requests.popleft()
            while self.errors and self.errors[0][0] < cutoff:
                self.errors.popleft()
    
    def get_error_rate(self):
        """Calculate current error rate"""
        with self.lock:
            if not self.requests:
                return 0
            return len(self.errors) / len(self.requests)
    
    def check_alerts(self):
        """Check if error rate exceeds threshold"""
        error_rate = self.get_error_rate()
        request_count = len(self.requests)
        
        if error_rate > self.alert_threshold and request_count >= 10:
            self._send_alert(error_rate, request_count)
    
    def _send_alert(self, error_rate, request_count):
        """Send alert for high error rate"""
        message = f"VBAPI Error Rate Alert: {error_rate:.1%} ({len(self.errors)}/{request_count} requests failed)"
        logger.critical(message)
        # Send to monitoring system, Slack, email, etc.
    
    def start_monitoring(self):
        """Start background monitoring thread"""
        def monitor_loop():
            while True:
                time.sleep(60)  # Check every minute
                self.check_alerts()
        
        thread = threading.Thread(target=monitor_loop, daemon=True)
        thread.start()

# Global monitor instance
api_monitor = VBAPIMonitor()

def monitored_api_call(url, headers, data=None):
    """API call with automatic monitoring"""
    try:
        response = requests.post(url, headers=headers, json=data)
        response.raise_for_status()
        api_monitor.record_request(success=True)
        return response.json()
    except requests.exceptions.HTTPError as e:
        error_type = f"HTTP_{e.response.status_code}"
        api_monitor.record_request(success=False, error_type=error_type)
        raise
    except Exception as e:
        error_type = type(e).__name__
        api_monitor.record_request(success=False, error_type=error_type)
        raise
```

## Best Practices Summary

### Error Handling

1. **Always capture Activity IDs** - Essential for support troubleshooting
2. **Implement proper retry logic** - Use exponential backoff for transient errors
3. **Monitor error patterns** - Track error rates and types
4. **Log comprehensively** - Include request details, timestamps, and context


### Resilience Patterns

1. **Circuit breakers** - Prevent cascading failures
2. **Timeouts** - Set reasonable request timeouts
3. **Graceful degradation** - Provide fallback mechanisms
4. **Health checks** - Monitor API availability


### Support Escalation

When escalating issues to VBA Support, always provide:

- **Activity ID** from the error response
- **Timestamp** of the error
- **Request details** (endpoint, method, headers)
- **Full error response**
- **Steps to reproduce** the issue


This comprehensive error handling strategy ensures your VBAPI integration remains robust and maintainable in production environments.