> ## Documentation Index
> Fetch the complete documentation index at: https://docs.transaction.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Best practices for handling API errors and edge cases

## Overview

Proper error handling is crucial for building robust applications with Transaction API. This guide covers common error scenarios, best practices, and implementation patterns.

## Error Response Format

All API errors follow a consistent format:

```json theme={null}
{
  "success": false,
  "error": {
    "status": 400,
    "code": "VALIDATION_INVALID",
    "message": "Invalid input data"
  },
  "data": null
}
```

## Common Error Scenarios

### Authentication Errors

<Columns cols={2}>
  <Card title="Invalid Token" icon="key">
    **Status:** 401
    **Code:** `UNAUTHORISED`

    ```json theme={null}
    {
      "success": false,
      "error": {
        "status": 401,
        "code": "UNAUTHORISED",
        "message": "Invalid or missing authentication"
      },
      "data": null
    }
    ```
  </Card>

  <Card title="Invalid Credentials" icon="user-x">
    **Status:** 401
    **Code:** `INVALID_CREDENTIALS`

    ```json theme={null}
    {
      "success": false,
      "error": {
        "status": 401,
        "code": "INVALID_CREDENTIALS",
        "message": "Invalid login credentials"
      },
      "data": null
    }
    ```
  </Card>
</Columns>

### Validation Errors

<Columns cols={2}>
  <Card title="Invalid Input" icon="exclamation-triangle">
    **Status:** 400
    **Code:** `VALIDATION_INVALID`

    ```json theme={null}
    {
      "success": false,
      "error": {
        "status": 400,
        "code": "VALIDATION_INVALID",
        "message": "Invalid input data"
      },
      "data": null
    }
    ```
  </Card>

  <Card title="Duplicate Entry" icon="copy">
    **Status:** 400
    **Code:** `DUPLICATE_ENTRY`

    ```json theme={null}
    {
      "success": false,
      "error": {
        "status": 400,
        "code": "DUPLICATE_ENTRY",
        "message": "Duplicate resource detected"
      },
      "data": null
    }
    ```
  </Card>
</Columns>

### Resource Errors

<Columns cols={2}>
  <Card title="Not Found" icon="magnifying-glass">
    **Status:** 404
    **Code:** `NOT_FOUND`

    ```json theme={null}
    {
      "success": false,
      "error": {
        "status": 404,
        "code": "NOT_FOUND",
        "message": "Resource not found"
      },
      "data": null
    }
    ```
  </Card>

  <Card title="Insufficient Balance" icon="wallet">
    **Status:** 400
    **Code:** `INSUFFICIENT_BALANCE`

    ```json theme={null}
    {
      "success": false,
      "error": {
        "status": 400,
        "code": "INSUFFICIENT_BALANCE",
        "message": "Insufficient wallet balance"
      },
      "data": null
    }
    ```
  </Card>
</Columns>

## Error Handling Patterns

### Basic Error Handling

```javascript theme={null}
async function makeApiRequest(url, options) {
  try {
    const response = await fetch(url, options);
    const data = await response.json();
    
    if (!data.success) {
      throw new ApiError(data.error);
    }
    
    return data.data;
  } catch (error) {
    if (error instanceof ApiError) {
      throw error;
    }
    
    // Handle network errors
    throw new NetworkError('Network request failed', error);
  }
}

class ApiError extends Error {
  constructor(error) {
    super(error.message);
    this.status = error.status;
    this.code = error.code;
    this.message = error.message;
  }
}

class NetworkError extends Error {
  constructor(message, originalError) {
    super(message);
    this.originalError = originalError;
  }
}
```

### Advanced Error Handling

```javascript theme={null}
class TransactionApiClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://api.transaction.gg';
  }
  
  async request(endpoint, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    const config = {
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
        ...options.headers
      },
      ...options
    };
    
    try {
      const response = await fetch(url, config);
      const data = await response.json();
      
      if (!data.success) {
        return this.handleApiError(data.error, response.status);
      }
      
      return data.data;
    } catch (error) {
      return this.handleNetworkError(error);
    }
  }
  
  handleApiError(error, status) {
    switch (error.code) {
      case 'UNAUTHORISED':
        return this.handleUnauthorized();
      case 'RATE_LIMITED':
        return this.handleRateLimit();
      case 'VALIDATION_INVALID':
        return this.handleValidationError(error);
      case 'NOT_FOUND':
        return this.handleNotFound(error);
      default:
        return this.handleGenericError(error);
    }
  }
  
  handleUnauthorized() {
    // Redirect to login or refresh token
    this.redirectToLogin();
    throw new ApiError({
      status: 401,
      code: 'UNAUTHORISED',
      message: 'Please log in again'
    });
  }
  
  handleRateLimit() {
    // Implement exponential backoff
    this.scheduleRetry();
    throw new ApiError({
      status: 429,
      code: 'RATE_LIMITED',
      message: 'Rate limit exceeded. Please try again later.'
    });
  }
  
  handleValidationError(error) {
    // Show field-specific validation errors
    this.showValidationErrors(error.details);
    throw new ApiError(error);
  }
  
  handleNotFound(error) {
    // Show user-friendly not found message
    this.showNotFoundMessage();
    throw new ApiError(error);
  }
  
  handleGenericError(error) {
    // Log error and show generic message
    console.error('API Error:', error);
    this.showGenericErrorMessage();
    throw new ApiError(error);
  }
  
  handleNetworkError(error) {
    // Handle network connectivity issues
    if (!navigator.onLine) {
      this.showOfflineMessage();
    } else {
      this.showNetworkErrorMessage();
    }
    throw new NetworkError('Network request failed', error);
  }
}
```

## Retry Logic

### Exponential Backoff

```javascript theme={null}
class RetryHandler {
  constructor(maxRetries = 3, baseDelay = 1000) {
    this.maxRetries = maxRetries;
    this.baseDelay = baseDelay;
  }
  
  async executeWithRetry(operation) {
    let lastError;
    
    for (let attempt = 0; attempt < this.maxRetries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error;
        
        if (!this.shouldRetry(error, attempt)) {
          throw error;
        }
        
        const delay = this.calculateDelay(attempt);
        await this.sleep(delay);
      }
    }
    
    throw lastError;
  }
  
  shouldRetry(error, attempt) {
    // Don't retry on client errors (4xx)
    if (error.status >= 400 && error.status < 500) {
      return false;
    }
    
    // Don't retry on authentication errors
    if (error.code === 'UNAUTHORISED') {
      return false;
    }
    
    // Retry on server errors (5xx) and network errors
    return error.status >= 500 || error instanceof NetworkError;
  }
  
  calculateDelay(attempt) {
    return this.baseDelay * Math.pow(2, attempt);
  }
  
  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}
```

### Circuit Breaker Pattern

```javascript theme={null}
class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.threshold = threshold;
    this.timeout = timeout;
    this.failureCount = 0;
    this.lastFailureTime = null;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
  }
  
  async execute(operation) {
    if (this.state === 'OPEN') {
      if (this.shouldAttemptReset()) {
        this.state = 'HALF_OPEN';
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }
    
    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }
  
  onFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    
    if (this.failureCount >= this.threshold) {
      this.state = 'OPEN';
    }
  }
  
  shouldAttemptReset() {
    return Date.now() - this.lastFailureTime > this.timeout;
  }
}
```

## User-Friendly Error Messages

### Error Message Mapping

```javascript theme={null}
const ERROR_MESSAGES = {
  'UNAUTHORISED': 'Please log in to continue',
  'INVALID_CREDENTIALS': 'Invalid email or password',
  'VALIDATION_INVALID': 'Please check your input and try again',
  'DUPLICATE_ENTRY': 'This item already exists',
  'EMAIL_ALREADY_REGISTERED': 'An account with this email already exists',
  'MERCHANT_NOT_FOUND': 'Merchant account not found',
  'PAYMENT_NOT_FOUND': 'Payment not found',
  'PRODUCT_NOT_FOUND': 'Product not found',
  'PRICE_NOT_FOUND': 'Price not found',
  'CUSTOMER_NOT_FOUND': 'Customer not found',
  'INSUFFICIENT_BALANCE': 'Insufficient balance for this operation',
  'RATE_LIMITED': 'Too many requests. Please try again later.'
};

function getUserFriendlyMessage(errorCode) {
  return ERROR_MESSAGES[errorCode] || 'An unexpected error occurred';
}
```

### Error Display Components

```javascript theme={null}
// React component for displaying errors
function ErrorDisplay({ error, onRetry }) {
  if (!error) return null;
  
  const message = getUserFriendlyMessage(error.code);
  const isRetryable = error.status >= 500 || error instanceof NetworkError;
  
  return (
    <div className="error-display">
      <div className="error-icon">⚠️</div>
      <div className="error-message">{message}</div>
      {isRetryable && (
        <button onClick={onRetry} className="retry-button">
          Try Again
        </button>
      )}
    </div>
  );
}
```

## Logging and Monitoring

### Error Logging

```javascript theme={null}
class ErrorLogger {
  constructor() {
    this.logLevel = process.env.NODE_ENV === 'production' ? 'error' : 'debug';
  }
  
  logError(error, context = {}) {
    const logEntry = {
      timestamp: new Date().toISOString(),
      level: 'error',
      message: error.message,
      code: error.code,
      status: error.status,
      context,
      stack: error.stack
    };
    
    // Log to console in development
    if (this.logLevel === 'debug') {
      console.error('API Error:', logEntry);
    }
    
    // Send to monitoring service in production
    if (this.logLevel === 'error') {
      this.sendToMonitoring(logEntry);
    }
  }
  
  sendToMonitoring(logEntry) {
    // Send to your monitoring service (e.g., Sentry, LogRocket, etc.)
    // Example with Sentry:
    // Sentry.captureException(error, { extra: context });
  }
}
```

### Error Metrics

```javascript theme={null}
class ErrorMetrics {
  constructor() {
    this.metrics = {
      totalErrors: 0,
      errorsByCode: {},
      errorsByStatus: {},
      errorRate: 0
    };
  }
  
  recordError(error) {
    this.metrics.totalErrors++;
    
    // Track errors by code
    if (error.code) {
      this.metrics.errorsByCode[error.code] = 
        (this.metrics.errorsByCode[error.code] || 0) + 1;
    }
    
    // Track errors by status
    if (error.status) {
      this.metrics.errorsByStatus[error.status] = 
        (this.metrics.errorsByStatus[error.status] || 0) + 1;
    }
    
    // Calculate error rate
    this.calculateErrorRate();
  }
  
  calculateErrorRate() {
    // Calculate error rate based on total requests
    // This would need to be integrated with your request tracking
  }
  
  getMetrics() {
    return this.metrics;
  }
}
```

## Testing Error Scenarios

### Unit Tests

```javascript theme={null}
describe('Error Handling', () => {
  test('handles authentication errors', async () => {
    const client = new TransactionApiClient('invalid_key');
    
    await expect(client.request('/auth/me'))
      .rejects.toThrow('UNAUTHORISED');
  });
  
  test('handles validation errors', async () => {
    const client = new TransactionApiClient('valid_key');
    
    await expect(client.request('/payment/create', {
      method: 'POST',
      body: JSON.stringify({ amount: 'invalid' })
    })).rejects.toThrow('VALIDATION_INVALID');
  });
  
  test('handles network errors', async () => {
    // Mock network failure
    global.fetch = jest.fn().mockRejectedValue(new Error('Network error'));
    
    const client = new TransactionApiClient('valid_key');
    
    await expect(client.request('/payment/create'))
      .rejects.toThrow('Network request failed');
  });
});
```

### Integration Tests

```javascript theme={null}
describe('Error Recovery', () => {
  test('retries on server errors', async () => {
    const retryHandler = new RetryHandler(3, 100);
    let attemptCount = 0;
    
    const operation = async () => {
      attemptCount++;
      if (attemptCount < 3) {
        throw new ApiError({ status: 500, code: 'SERVER_ERROR' });
      }
      return 'success';
    };
    
    const result = await retryHandler.executeWithRetry(operation);
    expect(result).toBe('success');
    expect(attemptCount).toBe(3);
  });
});
```

## Best Practices

<Steps>
  <Step title="Handle All Error Types">
    Implement specific handling for different error codes and scenarios.
  </Step>

  <Step title="Provide User Feedback">
    Show clear, actionable error messages to users.
  </Step>

  <Step title="Implement Retry Logic">
    Use exponential backoff for transient errors.
  </Step>

  <Step title="Log and Monitor">
    Track errors for debugging and system health monitoring.
  </Step>

  <Step title="Test Error Scenarios">
    Write tests for common error conditions.
  </Step>
</Steps>

## Common Pitfalls

<AccordionGroup>
  <Accordion title="Ignoring Error Codes">
    **Problem:** Treating all errors the same way

    **Solution:** Implement specific handling for different error codes
  </Accordion>

  <Accordion title="Not Retrying Transient Errors">
    **Problem:** Giving up on temporary failures

    **Solution:** Implement retry logic with exponential backoff
  </Accordion>

  <Accordion title="Exposing Technical Details">
    **Problem:** Showing raw error messages to users

    **Solution:** Map error codes to user-friendly messages
  </Accordion>

  <Accordion title="Not Logging Errors">
    **Problem:** Difficult to debug production issues

    **Solution:** Implement comprehensive error logging
  </Accordion>
</AccordionGroup>
