Skip to main content

Error Handling

Build robust applications by handling errors gracefully.

HTTP Status Codes

CodeMeaningAction
200SuccessProcess response
201CreatedResource created successfully
204No ContentDeletion successful
400Bad RequestFix request data
401UnauthorizedCheck API key / re-authenticate
403ForbiddenUser hasn't authorized / scope issue
404Not FoundResource doesn't exist
429Rate LimitedWait and retry
500Server ErrorRetry 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