Error Handling
Build robust applications by handling errors gracefully.
HTTP Status Codes
| Code | Meaning | Action |
|---|---|---|
| 200 | Success | Process response |
| 201 | Created | Resource created successfully |
| 204 | No Content | Deletion successful |
| 400 | Bad Request | Fix request data |
| 401 | Unauthorized | Check API key / re-authenticate |
| 403 | Forbidden | User hasn't authorized / scope issue |
| 404 | Not Found | Resource doesn't exist |
| 429 | Rate Limited | Wait and retry |
| 500 | Server Error | Retry with backoff |
Error Response Format
{
"error": {
"code": "validation_error",
"message": "Title is required",
"details": {
"field": "title",
"constraint": "required"
}
}
}
Handling in Python
from ofself import OfSelfClient
from ofself.exceptions import (
OfSelfError,
AuthenticationError,
PermissionDenied,
NotFoundError,
ValidationError,
RateLimitError,
ServerError,
)
client = OfSelfClient(api_key="your-key")
def get_user_nodes(user_id):
try:
return client.nodes.list(user_id=user_id)
except AuthenticationError:
# Invalid or expired API key
logging.error("Authentication failed")
raise AppError("Please check your API configuration")
except PermissionDenied:
# User revoked access or scope insufficient
logging.warning(f"No access to user {user_id}")
return redirect_to_reauthorize(user_id)
except NotFoundError:
# Resource doesn't exist
return []
except ValidationError as e:
# Bad request data
logging.error(f"Validation: {e.errors}")
raise AppError("Invalid request")
except RateLimitError as e:
# Too many requests
logging.warning(f"Rate limited, retry after {e.retry_after}s")
time.sleep(e.retry_after)
return get_user_nodes(user_id) # Retry
except ServerError:
# OfSelf server issue
logging.error("Server error")
raise AppError("Service temporarily unavailable")
except OfSelfError as e:
# Catch-all for other API errors
logging.error(f"API error: {e}")
raise AppError("An error occurred")
Handling in JavaScript
import {
OfSelfClient,
AuthenticationError,
PermissionDeniedError,
NotFoundError,
ValidationError,
RateLimitError,
} from '@ofself/sdk';
const client = new OfSelfClient({ apiKey: 'your-key' });
async function getUserNodes(userId: string) {
try {
return await client.nodes.list({ userId });
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Authentication failed');
throw new AppError('Please check your API configuration');
}
if (error instanceof PermissionDeniedError) {
console.warn(`No access to user ${userId}`);
return redirectToReauthorize(userId);
}
if (error instanceof NotFoundError) {
return [];
}
if (error instanceof ValidationError) {
console.error('Validation:', error.errors);
throw new AppError('Invalid request');
}
if (error instanceof RateLimitError) {
console.warn(`Rate limited, retry after ${error.retryAfter}s`);
await sleep(error.retryAfter * 1000);
return getUserNodes(userId); // Retry
}
console.error('Unknown error:', error);
throw new AppError('An error occurred');
}
}
Retry Logic with Exponential Backoff
import time
import random
def retry_with_backoff(func, max_retries=5, base_delay=1):
"""Retry a function with exponential backoff."""
for attempt in range(max_retries):
try:
return func()
except RateLimitError as e:
if e.retry_after:
time.sleep(e.retry_after)
else:
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
time.sleep(delay)
except ServerError:
if attempt == max_retries - 1:
raise
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
time.sleep(delay)
raise Exception("Max retries exceeded")
# Usage
nodes = retry_with_backoff(
lambda: client.nodes.list(user_id="user-123")
)
Circuit Breaker Pattern
For resilience against repeated failures:
from datetime import datetime, timedelta
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=60):
self.failure_count = 0
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.last_failure_time = None
self.state = "closed" # closed, open, half-open
def call(self, func):
if self.state == "open":
if datetime.now() - self.last_failure_time > timedelta(seconds=self.recovery_timeout):
self.state = "half-open"
else:
raise CircuitOpenError("Circuit is open")
try:
result = func()
self.on_success()
return result
except Exception as e:
self.on_failure()
raise
def on_success(self):
self.failure_count = 0
self.state = "closed"
def on_failure(self):
self.failure_count += 1
self.last_failure_time = datetime.now()
if self.failure_count >= self.failure_threshold:
self.state = "open"
# Usage
circuit = CircuitBreaker()
try:
result = circuit.call(lambda: client.nodes.list(user_id="user-123"))
except CircuitOpenError:
# Use cached data or show maintenance message
result = get_cached_nodes()
User-Friendly Error Messages
Map technical errors to user-friendly messages:
ERROR_MESSAGES = {
"authentication_error": "Please sign in again",
"permission_denied": "You don't have access to this data",
"not_found": "The requested item was not found",
"validation_error": "Please check your input",
"rate_limit": "Too many requests. Please wait a moment.",
"server_error": "Something went wrong. Please try again later.",
}
def get_user_message(error):
error_type = type(error).__name__.lower().replace("error", "_error")
return ERROR_MESSAGES.get(error_type, "An unexpected error occurred")
Logging Best Practices
import logging
logger = logging.getLogger(__name__)
def api_call_with_logging(func, context):
"""Wrap API calls with consistent logging."""
try:
result = func()
logger.info(f"API call successful: {context}")
return result
except OfSelfError as e:
logger.error(f"API error in {context}: {e}", extra={
"error_type": type(e).__name__,
"context": context,
# Don't log sensitive data like user_id or tokens
})
raise
Handling Webhook Errors
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook_handler():
try:
# Verify signature first
if not verify_signature(request):
return jsonify({"error": "Invalid signature"}), 401
# Process event
event = request.json
process_event(event)
return jsonify({"status": "ok"}), 200
except ValidationError as e:
# Bad payload - don't retry
logger.warning(f"Invalid webhook payload: {e}")
return jsonify({"error": "Invalid payload"}), 400
except Exception as e:
# Processing failed - OfSelf will retry
logger.error(f"Webhook processing failed: {e}")
return jsonify({"error": "Processing failed"}), 500
Checklist
- Handle all expected HTTP status codes
- Implement retry logic with backoff
- Don't retry on 4xx errors (except 429)
- Log errors for debugging (without sensitive data)
- Show user-friendly error messages
- Implement circuit breaker for resilience
- Gracefully handle webhook failures
- Return 200 quickly from webhooks, process async