SafePays API

Error Handling

Understanding and handling API errors effectively

Overview

The SafePays API uses standard HTTP status codes to indicate the success or failure of API requests. Error responses include a descriptive message to help you identify and fix issues.

Error Response Format

All error responses follow this consistent format:

{
  "error": "Descriptive error message"
}

HTTP Status Codes

Success Codes (2xx)

CodeNameDescription
200OKRequest successful
201CreatedResource created successfully

Client Error Codes (4xx)

CodeNameDescription
400Bad RequestInvalid request parameters or missing required fields
401UnauthorizedInvalid or missing API key
404Not FoundResource not found
409ConflictResource already exists (e.g., duplicate email)
429Too Many RequestsRate limit exceeded

Server Error Codes (5xx)

CodeNameDescription
500Internal Server ErrorServer error - retry or contact support
502Bad GatewayTemporary server issue - retry
503Service UnavailableService temporarily unavailable
504Gateway TimeoutRequest timeout - retry

Common Error Messages

Authentication Errors

Invalid API Key

{
  "error": "Invalid API Key"
}
  • Cause: API key is incorrect or has been revoked
  • Solution: Verify your API key in the dashboard

Validation Errors

Missing Required Fields

{
  "error": "name and email are required"
}
  • Cause: Required fields not provided in request
  • Solution: Include all required fields in your request

Invalid Email Format

{
  "error": "Invalid email format"
}
  • Cause: Email doesn't match valid email pattern
  • Solution: Provide a valid email address

Resource Errors

Customer Not Found

{
  "error": "Customer not found"
}
  • Cause: Customer ID doesn't exist
  • Solution: Verify customer ID or create customer first

Duplicate Customer

{
  "error": "Customer with this email already exists"
}
  • Cause: Email address already registered
  • Solution: Use existing customer or different email

Error Handling Best Practices

1. Implement Retry Logic

For transient errors (5xx, 429), implement exponential backoff:

async function apiCallWithRetry(url, options, maxRetries = 3) {
  let lastError;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);
      
      if (response.ok) {
        return await response.json();
      }
      
      // Don't retry client errors (4xx) except rate limits
      if (response.status >= 400 && response.status < 500 && response.status !== 429) {
        const error = await response.json();
        throw new Error(`API Error: ${error.error}`);
      }
      
      // Retry server errors and rate limits
      lastError = new Error(`HTTP ${response.status}`);
      
      // Exponential backoff
      const delay = Math.min(1000 * Math.pow(2, i), 10000);
      await new Promise(resolve => setTimeout(resolve, delay));
      
    } catch (error) {
      lastError = error;
      
      // Network errors - retry
      if (error.name === 'TypeError') {
        const delay = Math.min(1000 * Math.pow(2, i), 10000);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      
      throw error;
    }
  }
  
  throw lastError;
}

// Usage
try {
  const result = await apiCallWithRetry(
    'https://app.safepays.com/api/customer',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    }
  );
  console.log('Success:', result);
} catch (error) {
  console.error('Failed after retries:', error);
}
import time
import requests
from typing import Optional, Dict, Any

def api_call_with_retry(
    url: str,
    method: str = 'GET',
    data: Optional[Dict[str, Any]] = None,
    max_retries: int = 3
) -> Dict[str, Any]:
    last_error = None
    
    for attempt in range(max_retries):
        try:
            if method == 'POST':
                response = requests.post(url, json=data)
            else:
                response = requests.get(url, params=data)
            
            # Success
            if response.status_code in range(200, 300):
                return response.json()
            
            # Don't retry client errors (except rate limits)
            if 400 <= response.status_code < 500 and response.status_code != 429:
                error_data = response.json()
                raise Exception(f"API Error: {error_data.get('error', 'Unknown error')}")
            
            # Retry server errors and rate limits
            last_error = f"HTTP {response.status_code}"
            
            # Exponential backoff
            delay = min(2 ** attempt, 10)
            time.sleep(delay)
            
        except requests.exceptions.RequestException as e:
            last_error = str(e)
            # Network error - retry with backoff
            delay = min(2 ** attempt, 10)
            time.sleep(delay)
    
    raise Exception(f"Failed after {max_retries} retries: {last_error}")

# Usage
try:
    result = api_call_with_retry(
        'https://app.safepays.com/api/customer',
        method='POST',
        data={
            'api_key': 'your_api_key',
            'name': 'John Doe',
            'email': 'john@example.com'
        }
    )
    print('Success:', result)
except Exception as e:
    print('Failed:', e)
<?php
function apiCallWithRetry($url, $method = 'GET', $data = null, $maxRetries = 3) {
    $lastError = null;
    
    for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        
        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        }
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        curl_close($ch);
        
        // Network error
        if ($response === false) {
            $lastError = "Network error: $curlError";
            sleep(min(pow(2, $attempt), 10));
            continue;
        }
        
        // Success
        if ($httpCode >= 200 && $httpCode < 300) {
            return json_decode($response, true);
        }
        
        // Don't retry client errors (except rate limits)
        if ($httpCode >= 400 && $httpCode < 500 && $httpCode !== 429) {
            $error = json_decode($response, true);
            throw new Exception("API Error: " . ($error['error'] ?? 'Unknown error'));
        }
        
        // Retry server errors and rate limits
        $lastError = "HTTP $httpCode";
        sleep(min(pow(2, $attempt), 10));
    }
    
    throw new Exception("Failed after $maxRetries retries: $lastError");
}

// Usage
try {
    $result = apiCallWithRetry(
        'https://app.safepays.com/api/customer',
        'POST',
        [
            'api_key' => 'your_api_key',
            'name' => 'John Doe',
            'email' => 'john@example.com'
        ]
    );
    echo "Success: " . json_encode($result);
} catch (Exception $e) {
    echo "Failed: " . $e->getMessage();
}
?>

2. Log Errors for Debugging

Always log error responses for troubleshooting:

function logApiError(endpoint, error, context) {
  console.error('API Error', {
    timestamp: new Date().toISOString(),
    endpoint,
    error,
    context,
    // Include request ID if available
    requestId: error.headers?.['x-request-id']
  });
  
  // Send to error tracking service
  if (typeof Sentry !== 'undefined') {
    Sentry.captureException(new Error(`API Error: ${error.message}`), {
      extra: { endpoint, context }
    });
  }
}

3. User-Friendly Error Messages

Map API errors to user-friendly messages:

const ERROR_MESSAGES = {
  'Invalid API Key': 'Authentication failed. Please check your configuration.',
  'Customer not found': 'Customer record not found. Please verify the customer ID.',
  'Customer with this email already exists': 'A customer with this email already exists.',
  'webhook parameter is required': 'Webhook URL is required for invoice creation.',
  'Rate limit exceeded': 'Too many requests. Please try again later.',
  // Default
  'default': 'An error occurred. Please try again or contact support.'
};

function getUserMessage(apiError) {
  return ERROR_MESSAGES[apiError] || ERROR_MESSAGES.default;
}

4. Handle Network Timeouts

Set appropriate timeouts for API calls:

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000); // 10 second timeout

try {
  const response = await fetch(url, {
    ...options,
    signal: controller.signal
  });
  clearTimeout(timeout);
  // Process response
} catch (error) {
  if (error.name === 'AbortError') {
    console.error('Request timeout');
    // Handle timeout
  }
}

Rate Limiting

Rate limits help ensure API stability and fair usage for all users.

Current Limits

  • 1000 requests per minute
  • 10,000 requests per hour

Rate Limit Headers

Response headers indicate your current rate limit status:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 950
X-RateLimit-Reset: 1642089600

Handling Rate Limits

When rate limited, you'll receive a 429 response:

{
  "error": "Rate limit exceeded. Please retry after 60 seconds."
}

Best practices:

  1. Implement exponential backoff
  2. Cache responses when possible
  3. Batch operations where applicable
  4. Monitor rate limit headers

Debugging Tips

1. Check Request Format

Common issues:

  • Missing Content-Type header
  • Invalid JSON syntax
  • Wrong endpoint URL
  • Missing required fields

2. Verify API Key

  • Ensure no extra spaces or characters
  • Check if key is for correct environment (test/production)
  • Verify key hasn't been revoked

3. Use Request IDs

Include a unique request ID for tracking:

const requestId = generateUUID();
const response = await fetch(url, {
  headers: {
    'Content-Type': 'application/json',
    'X-Request-ID': requestId
  },
  // ...
});

4. Test with cURL

Test basic connectivity:

curl -X POST https://app.safepays.com/api/customer \
  -H "Content-Type: application/json" \
  -d '{"api_key":"test","name":"Test","email":"test@example.com"}' \
  -v

Error Recovery Strategies

Idempotency

Make operations idempotent to safely retry:

// Generate idempotency key
const idempotencyKey = generateIdempotencyKey(customerId, invoiceData);

// Include in request
const response = await fetch(url, {
  headers: {
    'Content-Type': 'application/json',
    'Idempotency-Key': idempotencyKey
  },
  // ...
});

Graceful Degradation

Handle API failures without breaking your application:

async function createInvoiceWithFallback(invoiceData) {
  try {
    // Try API call
    return await createInvoice(invoiceData);
  } catch (error) {
    // Log error
    console.error('Invoice creation failed:', error);
    
    // Queue for retry
    await queueInvoiceForRetry(invoiceData);
    
    // Return fallback response
    return {
      status: 'pending',
      message: 'Invoice queued for processing',
      retryAfter: 300 // 5 minutes
    };
  }
}

Getting Help

If you continue experiencing issues:

  1. Check the Dashboard: View API logs and error details
  2. Review Documentation: Ensure you're using endpoints correctly
  3. Contact Support: Email support@safepays.com with:
    • Request/Response examples
    • Error messages
    • Timestamps
    • API key (first 8 characters only)

On this page