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

# Errors

# Error Handling

Understanding and handling errors is crucial for building robust integrations with the Billbora API. This guide covers all error types, response formats, and best practices.

## Error Response Format

All Billbora API errors follow a consistent JSON structure:

```json theme={null}
{
  "error": {
    "code": "validation_error",
    "message": "Invalid input data",
    "details": {
      "email": ["Must be a valid email address"],
      "amount": ["Must be a positive number"]
    }
  },
  "meta": {
    "request_id": "req_abc123xyz",
    "timestamp": "2025-01-15T10:30:00Z"
  }
}
```

## HTTP Status Codes

<AccordionGroup>
  <Accordion title="2xx Success">
    * **200 OK**: Request successful
    * **201 Created**: Resource created successfully
    * **204 No Content**: Resource deleted successfully
  </Accordion>

  <Accordion title="4xx Client Errors">
    * **400 Bad Request**: Invalid request parameters
    * **401 Unauthorized**: Authentication required
    * **403 Forbidden**: Insufficient permissions
    * **404 Not Found**: Resource not found
    * **409 Conflict**: Resource conflict (duplicate data)
    * **422 Unprocessable Entity**: Validation failed
    * **429 Too Many Requests**: Rate limit exceeded
  </Accordion>

  <Accordion title="5xx Server Errors">
    * **500 Internal Server Error**: Unexpected server error
    * **502 Bad Gateway**: Upstream service error
    * **503 Service Unavailable**: Service temporarily unavailable
    * **504 Gateway Timeout**: Request timeout
  </Accordion>
</AccordionGroup>

## Common Error Codes

### Authentication Errors

<CodeGroup>
  ```json missing_token theme={null}
  {
    "error": {
      "code": "missing_token",
      "message": "Authentication token is required"
    }
  }
  ```

  ```json invalid_token theme={null}
  {
    "error": {
      "code": "invalid_token", 
      "message": "The provided token is invalid or expired"
    }
  }
  ```

  ```json insufficient_permissions theme={null}
  {
    "error": {
      "code": "insufficient_permissions",
      "message": "You don't have permission to access this resource"
    }
  }
  ```
</CodeGroup>

### Validation Errors

<CodeGroup>
  ```json validation_error theme={null}
  {
    "error": {
      "code": "validation_error",
      "message": "Invalid input data",
      "details": {
        "email": ["Must be a valid email address"],
        "line_items": ["At least one line item is required"]
      }
    }
  }
  ```

  ```json missing_required_field theme={null}
  {
    "error": {
      "code": "missing_required_field",
      "message": "Required field is missing",
      "details": {
        "client": ["This field is required"]
      }
    }
  }
  ```
</CodeGroup>

### Resource Errors

<CodeGroup>
  ```json not_found theme={null}
  {
    "error": {
      "code": "not_found",
      "message": "The requested resource was not found",
      "details": {
        "resource": "invoice",
        "id": "inv_1234567890"
      }
    }
  }
  ```

  ```json already_exists theme={null}
  {
    "error": {
      "code": "already_exists",
      "message": "A resource with this identifier already exists",
      "details": {
        "field": "invoice_number",
        "value": "INV-001"
      }
    }
  }
  ```
</CodeGroup>

### Rate Limiting

```json rate_limit_exceeded theme={null}
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Please try again later.",
    "details": {
      "limit": 1000,
      "reset_time": "2025-01-15T11:00:00Z",
      "retry_after": 3600
    }
  }
}
```

## Error Handling Best Practices

### 1. Always Check Status Codes

<CodeGroup>
  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.billbora.com/api/v1/invoices/', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(invoiceData)
  })

  if (!response.ok) {
    const error = await response.json()
    throw new Error(`API Error: ${error.error.message}`)
  }

  const invoice = await response.json()
  ```

  ```python Python theme={null}
  import requests

  try:
      response = requests.post(
          'https://api.billbora.com/api/v1/invoices/',
          headers={'Authorization': 'Bearer YOUR_API_KEY'},
          json=invoice_data
      )
      response.raise_for_status()
      invoice = response.json()
      
  except requests.exceptions.HTTPError as e:
      error_data = response.json()
      print(f"API Error: {error_data['error']['message']}")
      
  except requests.exceptions.RequestException as e:
      print(f"Network Error: {e}")
  ```
</CodeGroup>

### 2. Handle Specific Error Types

<CodeGroup>
  ```typescript TypeScript theme={null}
  enum BillboraErrorCode {
    VALIDATION_ERROR = 'validation_error',
    NOT_FOUND = 'not_found',
    RATE_LIMITED = 'rate_limit_exceeded',
    INSUFFICIENT_PERMISSIONS = 'insufficient_permissions'
  }

  class BillboraError extends Error {
    constructor(
      public code: BillboraErrorCode,
      public message: string,
      public details?: Record<string, string[]>,
      public statusCode?: number
    ) {
      super(message)
      this.name = 'BillboraError'
    }
  }

  const handleApiError = (error: any): never => {
    switch (error.error.code) {
      case BillboraErrorCode.VALIDATION_ERROR:
        // Show field-specific validation errors
        const fieldErrors = error.error.details
        throw new BillboraError(
          BillboraErrorCode.VALIDATION_ERROR,
          'Please fix the validation errors',
          fieldErrors,
          400
        )
        
      case BillboraErrorCode.NOT_FOUND:
        throw new BillboraError(
          BillboraErrorCode.NOT_FOUND,
          'The requested resource was not found',
          undefined,
          404
        )
        
      case BillboraErrorCode.RATE_LIMITED:
        const retryAfter = error.error.details?.retry_after || 3600
        throw new BillboraError(
          BillboraErrorCode.RATE_LIMITED,
          `Rate limit exceeded. Retry after ${retryAfter} seconds`,
          undefined,
          429
        )
        
      default:
        throw new BillboraError(
          error.error.code,
          error.error.message,
          error.error.details,
          error.status
        )
    }
  }
  ```
</CodeGroup>

### 3. Implement Retry Logic

<CodeGroup>
  ```javascript Exponential Backoff theme={null}
  async function apiCallWithRetry(apiCall, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await apiCall()
      } catch (error) {
        if (error.status === 429) {
          // Rate limited - wait and retry
          const retryAfter = error.details?.retry_after || Math.pow(2, attempt) * 1000
          await new Promise(resolve => setTimeout(resolve, retryAfter))
          continue
        }
        
        if (error.status >= 500 && attempt < maxRetries) {
          // Server error - retry with exponential backoff
          const delay = Math.pow(2, attempt) * 1000
          await new Promise(resolve => setTimeout(resolve, delay))
          continue
        }
        
        // Don't retry client errors (4xx)
        throw error
      }
    }
  }

  // Usage
  const invoice = await apiCallWithRetry(() => 
    createInvoice(invoiceData)
  )
  ```

  ```python Python Retry Decorator theme={null}
  import time
  import random
  from functools import wraps

  def retry_api_call(max_retries=3, backoff_factor=1):
      def decorator(func):
          @wraps(func)
          def wrapper(*args, **kwargs):
              for attempt in range(max_retries + 1):
                  try:
                      return func(*args, **kwargs)
                  except requests.exceptions.HTTPError as e:
                      if e.response.status_code == 429:
                          # Rate limited
                          retry_after = e.response.headers.get('Retry-After', 60)
                          time.sleep(int(retry_after))
                          continue
                      elif e.response.status_code >= 500 and attempt < max_retries:
                          # Server error - exponential backoff
                          delay = backoff_factor * (2 ** attempt) + random.uniform(0, 1)
                          time.sleep(delay)
                          continue
                      else:
                          raise
                  except requests.exceptions.RequestException as e:
                      if attempt < max_retries:
                          delay = backoff_factor * (2 ** attempt)
                          time.sleep(delay)
                          continue
                      else:
                          raise
              return None
          return wrapper
      return decorator

  @retry_api_call(max_retries=3)
  def create_invoice(invoice_data):
      response = requests.post(
          'https://api.billbora.com/api/v1/invoices/',
          headers={'Authorization': 'Bearer YOUR_API_KEY'},
          json=invoice_data
      )
      response.raise_for_status()
      return response.json()
  ```
</CodeGroup>

## Rate Limiting

### Understanding Rate Limits

Billbora implements rate limiting to ensure fair usage:

* **General API**: 1,000 requests per hour per user
* **Authentication**: 10 requests per minute per IP
* **Bulk operations**: 100 requests per hour per user

### Rate Limit Headers

Every API response includes rate limit information:

```http theme={null}
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1642694400
X-RateLimit-Reset-After: 3600
```

### Handling Rate Limits

<CodeGroup>
  ```javascript Check Headers theme={null}
  const response = await fetch('https://api.billbora.com/api/v1/invoices/')

  const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'))
  const resetTime = parseInt(response.headers.get('X-RateLimit-Reset'))

  if (remaining < 10) {
    console.warn('Rate limit nearly exceeded')
    // Consider slowing down requests
  }

  if (response.status === 429) {
    const retryAfter = response.headers.get('Retry-After')
    console.log(`Rate limited. Retry after ${retryAfter} seconds`)
  }
  ```

  ```python Monitor Usage theme={null}
  import requests
  import time

  class RateLimitedClient:
      def __init__(self, api_key):
          self.api_key = api_key
          self.remaining_requests = None
          self.reset_time = None
      
      def make_request(self, method, url, **kwargs):
          # Check if we're close to rate limit
          if self.remaining_requests and self.remaining_requests < 10:
              print("Warning: Approaching rate limit")
              time.sleep(1)  # Slow down requests
          
          response = requests.request(
              method, url,
              headers={'Authorization': f'Bearer {self.api_key}'},
              **kwargs
          )
          
          # Update rate limit info
          self.remaining_requests = int(response.headers.get('X-RateLimit-Remaining', 0))
          self.reset_time = int(response.headers.get('X-RateLimit-Reset', 0))
          
          if response.status_code == 429:
              retry_after = int(response.headers.get('Retry-After', 60))
              print(f"Rate limited. Waiting {retry_after} seconds...")
              time.sleep(retry_after)
              return self.make_request(method, url, **kwargs)
          
          response.raise_for_status()
          return response
  ```
</CodeGroup>

## Debugging API Errors

### 1. Use Request IDs

Every API response includes a request ID for debugging:

```json theme={null}
{
  "meta": {
    "request_id": "req_abc123xyz",
    "timestamp": "2025-01-15T10:30:00Z"
  }
}
```

When contacting support, always include the request ID.

### 2. Enable Request Logging

<CodeGroup>
  ```javascript Request Logging theme={null}
  const originalFetch = fetch

  window.fetch = async (...args) => {
    const [url, options] = args
    const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
    
    console.log(`[${requestId}] Request:`, {
      url,
      method: options?.method || 'GET',
      headers: options?.headers,
      body: options?.body
    })
    
    try {
      const response = await originalFetch(...args)
      const clonedResponse = response.clone()
      const responseData = await clonedResponse.json()
      
      console.log(`[${requestId}] Response:`, {
        status: response.status,
        headers: Object.fromEntries(response.headers.entries()),
        data: responseData
      })
      
      return response
    } catch (error) {
      console.error(`[${requestId}] Error:`, error)
      throw error
    }
  }
  ```

  ```python Request Logging theme={null}
  import requests
  import logging

  # Configure logging
  logging.basicConfig(level=logging.DEBUG)
  logger = logging.getLogger(__name__)

  # Enable requests debugging
  import http.client as http_client
  http_client.HTTPConnection.debuglevel = 1

  class LoggingSession(requests.Session):
      def request(self, method, url, **kwargs):
          logger.info(f"Making {method} request to {url}")
          if 'json' in kwargs:
              logger.info(f"Request body: {kwargs['json']}")
          
          response = super().request(method, url, **kwargs)
          
          logger.info(f"Response status: {response.status_code}")
          logger.info(f"Response headers: {dict(response.headers)}")
          
          try:
              response_json = response.json()
              logger.info(f"Response body: {response_json}")
          except:
              logger.info(f"Response body: {response.text}")
          
          return response

  # Usage
  session = LoggingSession()
  response = session.post('https://api.billbora.com/api/v1/invoices/', json=data)
  ```
</CodeGroup>

### 3. Validation Error Details

For validation errors, the API provides detailed field-level error messages:

```json theme={null}
{
  "error": {
    "code": "validation_error",
    "message": "Invalid input data",
    "details": {
      "line_items": [
        "At least one line item is required"
      ],
      "line_items.0.unit_price": [
        "Must be a valid monetary amount"
      ],
      "client": [
        "This field is required"
      ]
    }
  }
}
```

Use these details to show specific error messages to users.

## Error Recovery Strategies

### 1. Graceful Degradation

```javascript theme={null}
async function createInvoiceWithFallback(invoiceData) {
  try {
    return await billboraAPI.invoices.create(invoiceData)
  } catch (error) {
    if (error.code === 'validation_error') {
      // Show validation errors to user
      showValidationErrors(error.details)
      return null
    }
    
    if (error.code === 'rate_limit_exceeded') {
      // Queue for later processing
      queueForLater(invoiceData)
      showMessage('Invoice queued for processing')
      return null
    }
    
    // For other errors, show generic message and log details
    logError(error)
    showMessage('Something went wrong. Please try again.')
    return null
  }
}
```

### 2. Offline Support

```javascript theme={null}
class OfflineInvoiceQueue {
  constructor() {
    this.queue = JSON.parse(localStorage.getItem('invoice_queue') || '[]')
  }
  
  add(invoiceData) {
    this.queue.push({
      id: Date.now(),
      data: invoiceData,
      timestamp: new Date().toISOString()
    })
    localStorage.setItem('invoice_queue', JSON.stringify(this.queue))
  }
  
  async processQueue() {
    const items = [...this.queue]
    this.queue = []
    localStorage.setItem('invoice_queue', JSON.stringify(this.queue))
    
    for (const item of items) {
      try {
        await billboraAPI.invoices.create(item.data)
        console.log(`Processed queued invoice ${item.id}`)
      } catch (error) {
        // Re-queue if still failing
        this.add(item.data)
        console.error(`Failed to process queued invoice ${item.id}:`, error)
      }
    }
  }
}
```

## Getting Help

When you encounter errors that you can't resolve:

<CardGroup cols={2}>
  <Card title="Check Status Page" icon="chart-line" href="https://status.billbora.com">
    See if there are any ongoing service issues
  </Card>

  <Card title="Search Documentation" icon="magnifying-glass" href="/api-reference">
    Look up specific error codes and solutions
  </Card>

  <Card title="Community Forum" icon="users" href="https://community.billbora.com">
    Ask questions and get help from other developers
  </Card>

  <Card title="Contact Support" icon="life-ring" href="mailto:api-support@billbora.com">
    Reach out to our support team with request IDs
  </Card>
</CardGroup>

<Info>
  **Pro Tip**: Always include the request ID when reporting issues. It helps our support team diagnose problems much faster.
</Info>
