Error Handling¶
The SDK provides structured error handling to help you manage API failures gracefully.
Exception Types¶
FoxnoseAPIError¶
The base exception for all API-related errors:
from foxnose_sdk.errors import FoxnoseAPIError
try:
resource = client.get_resource("folder-key", "resource-key")
except FoxnoseAPIError as e:
print(f"Status: {e.status_code}")
print(f"Message: {e.message}")
print(f"Details: {e.details}")
Attributes¶
| Attribute | Type | Description |
|---|---|---|
status_code |
int |
HTTP status code |
message |
str |
Error message from the API |
details |
dict \| None |
Additional error details (if provided) |
Common Error Codes¶
400 Bad Request¶
Invalid request data or parameters:
try:
folder = client.create_folder({
"name": "", # Invalid: empty name
})
except FoxnoseAPIError as e:
if e.status_code == 400:
print("Validation error:", e.message)
401 Unauthorized¶
Authentication failed or token expired:
try:
folders = client.list_folders()
except FoxnoseAPIError as e:
if e.status_code == 401:
print("Authentication failed - check your token")
403 Forbidden¶
Insufficient permissions:
try:
client.delete_folder("protected-folder")
except FoxnoseAPIError as e:
if e.status_code == 403:
print("Permission denied - you don't have access")
404 Not Found¶
Resource doesn't exist:
try:
folder = client.get_folder("non-existent")
except FoxnoseAPIError as e:
if e.status_code == 404:
print("Folder not found")
409 Conflict¶
Resource already exists or conflict with current state:
try:
folder = client.create_folder({
"name": "Blog",
"alias": "blog", # Already exists
})
except FoxnoseAPIError as e:
if e.status_code == 409:
print("A folder with this alias already exists")
422 Unprocessable Entity¶
Validation errors:
try:
revision = client.publish_revision(
"folder-key",
"resource-key",
"revision-key",
)
except FoxnoseAPIError as e:
if e.status_code == 422:
print("Validation failed:", e.details)
429 Too Many Requests¶
Rate limit exceeded:
import time
try:
for i in range(1000):
client.list_folders()
except FoxnoseAPIError as e:
if e.status_code == 429:
retry_after = e.details.get("retry_after", 60)
print(f"Rate limited. Retry after {retry_after} seconds")
time.sleep(retry_after)
500+ Server Errors¶
Server-side errors:
try:
folders = client.list_folders()
except FoxnoseAPIError as e:
if e.status_code >= 500:
print("Server error - try again later")
Error Handling Patterns¶
Comprehensive Handler¶
from foxnose_sdk.errors import FoxnoseAPIError
def handle_api_call(func, *args, **kwargs):
"""Wrapper with comprehensive error handling."""
try:
return func(*args, **kwargs)
except FoxnoseAPIError as e:
if e.status_code == 401:
raise AuthenticationError("Token expired or invalid")
elif e.status_code == 403:
raise PermissionError(f"Access denied: {e.message}")
elif e.status_code == 404:
return None # Resource not found
elif e.status_code == 429:
raise RateLimitError("Too many requests")
elif e.status_code >= 500:
raise ServiceUnavailableError("API is temporarily unavailable")
else:
raise
# Usage
folder = handle_api_call(client.get_folder, "my-folder")
if folder is None:
print("Folder not found")
Retry Pattern¶
import time
from foxnose_sdk.errors import FoxnoseAPIError
def with_retry(func, max_retries=3, backoff=1.0):
"""Execute function with exponential backoff retry."""
last_error = None
for attempt in range(max_retries):
try:
return func()
except FoxnoseAPIError as e:
last_error = e
# Don't retry client errors (except rate limiting)
if 400 <= e.status_code < 500 and e.status_code != 429:
raise
# Calculate delay with exponential backoff
delay = backoff * (2 ** attempt)
print(f"Attempt {attempt + 1} failed, retrying in {delay}s...")
time.sleep(delay)
raise last_error
# Usage
folders = with_retry(client.list_folders)
Async Error Handling¶
from foxnose_sdk.errors import FoxnoseAPIError
async def fetch_resources_safely(client, folder_key):
"""Fetch resources with error handling."""
try:
return await client.list_resources(folder_key)
except FoxnoseAPIError as e:
if e.status_code == 404:
print(f"Folder {folder_key} not found")
return None
raise
Validation Errors¶
When creating or updating resources, validation errors include field-level details:
try:
resource = client.create_resource("folder-key", {
"data": {
"title": "", # Required field is empty
"email": "invalid-email", # Invalid format
},
})
except FoxnoseAPIError as e:
if e.status_code in (400, 422):
if e.details and "errors" in e.details:
for field, messages in e.details["errors"].items():
print(f" {field}: {', '.join(messages)}")
Network Errors¶
Network-level errors (timeouts, connection failures) raise standard Python exceptions:
import httpx
from foxnose_sdk.errors import FoxnoseAPIError
try:
folders = client.list_folders()
except httpx.TimeoutException:
print("Request timed out")
except httpx.ConnectError:
print("Could not connect to the API")
except FoxnoseAPIError as e:
print(f"API error: {e.message}")
Best Practices¶
- Be specific - Catch specific error codes rather than catching all errors
- Log errors - Always log error details for debugging
- Graceful degradation - Handle 404s gracefully in user-facing applications
- Retry wisely - Only retry on server errors and rate limits
- Show user-friendly messages - Don't expose raw API errors to end users