Error Handling
Error Handling Guide
This guide explains how to handle errors and edge cases when using the Spreetail Channel Integration API.
Overview
The API uses standard HTTP status codes and returns error responses in a consistent JSON format. Understanding these errors and implementing proper error handling is crucial for building robust integrations.
HTTP Status Codes
| Status Code | Meaning | Description |
|---|---|---|
200 | OK | Request successful |
400 | Bad Request | Invalid request data or validation error |
401 | Unauthorized | Authentication failed or token expired |
403 | Forbidden | Insufficient permissions |
404 | Not Found | Resource not found |
409 | Conflict | Duplicate resource (e.g., duplicate order reference) |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Server-side error |
503 | Service Unavailable | Service temporarily unavailable |
Error Response Format
All error responses follow this structure:
{
"success": false,
"error": "Error message describing what went wrong"
}Some errors may include additional fields:
{
"success": false,
"error": "Validation failed",
"details": {
"field": "ReferenceNumber",
"message": "ReferenceNumber is required"
}
}Common Errors
400 Bad Request
Invalid Request Data
{
"success": false,
"error": "ReferenceNumber is required"
}Invalid Format
{
"success": false,
"error": "Invalid date format. Expected ISO 8601 format (YYYY-MM-DDTHH:mm:ssZ)"
}Validation Error
{
"success": false,
"error": "OrderItems must contain at least one item"
}401 Unauthorized
Missing Token
{
"success": false,
"error": "Authorization header is required"
}Invalid Token
{
"success": false,
"error": "Invalid or expired token"
}Invalid Credentials
{
"success": false,
"error": "Invalid credentials"
}409 Conflict
Duplicate Reference Number
{
"success": false,
"error": "Order with ReferenceNumber 'ORD-2025-001' already exists"
}429 Too Many Requests
Rate Limit Exceeded
{
"success": false,
"error": "Rate limit exceeded. Please retry after 60 seconds"
}Error Handling Patterns
Python Example
import requests
import time
from typing import Optional, Dict, Any
class APIError(Exception):
"""Base exception for API errors"""
def __init__(self, message: str, status_code: int = None, response: Dict = None):
self.message = message
self.status_code = status_code
self.response = response
super().__init__(self.message)
def make_request_with_retry(
method: str,
url: str,
headers: Dict[str, str],
max_retries: int = 3,
retry_delay: int = 1,
**kwargs
) -> Dict[str, Any]:
"""Make API request with automatic retry logic"""
for attempt in range(max_retries):
try:
response = requests.request(method, url, headers=headers, **kwargs)
# Handle rate limiting
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', retry_delay * (attempt + 1)))
if attempt < max_retries - 1:
time.sleep(retry_after)
continue
else:
raise APIError(
"Rate limit exceeded. Please try again later.",
status_code=429,
response=response.json()
)
# Handle authentication errors
if response.status_code == 401:
raise APIError(
"Authentication failed. Token may be expired.",
status_code=401,
response=response.json()
)
# Handle client errors (4xx)
if 400 <= response.status_code < 500:
error_data = response.json()
raise APIError(
error_data.get('error', 'Client error'),
status_code=response.status_code,
response=error_data
)
# Handle server errors (5xx)
if response.status_code >= 500:
if attempt < max_retries - 1:
time.sleep(retry_delay * (attempt + 1))
continue
else:
error_data = response.json() if response.content else {}
raise APIError(
"Server error. Please try again later.",
status_code=response.status_code,
response=error_data
)
# Success
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
if attempt < max_retries - 1:
time.sleep(retry_delay * (attempt + 1))
continue
else:
raise APIError(f"Request failed: {str(e)}")
raise APIError("Max retries exceeded")
# Usage
try:
result = make_request_with_retry(
'POST',
'https://api.spreetaileu.com/api/api/v1/outbound',
headers={'Authorization': f'Bearer {token}'},
json=order_data
)
print("Order created successfully:", result)
except APIError as e:
if e.status_code == 409:
print(f"Duplicate order: {e.message}")
elif e.status_code == 400:
print(f"Validation error: {e.message}")
elif e.status_code == 401:
print("Authentication failed - refreshing token...")
# Refresh token logic here
else:
print(f"Error ({e.status_code}): {e.message}")JavaScript/Node.js Example
class APIError extends Error {
constructor(message, statusCode, response) {
super(message);
this.name = 'APIError';
this.statusCode = statusCode;
this.response = response;
}
}
async function makeRequestWithRetry(
url,
options = {},
maxRetries = 3,
retryDelay = 1000
) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
// Handle rate limiting
if (response.status === 429) {
const retryAfter = parseInt(
response.headers.get('Retry-After') || retryDelay * (attempt + 1)
);
if (attempt < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
} else {
const errorData = await response.json();
throw new APIError(
'Rate limit exceeded. Please try again later.',
429,
errorData
);
}
}
// Handle authentication errors
if (response.status === 401) {
const errorData = await response.json();
throw new APIError(
'Authentication failed. Token may be expired.',
401,
errorData
);
}
// Handle client errors (4xx)
if (response.status >= 400 && response.status < 500) {
const errorData = await response.json();
throw new APIError(
errorData.error || 'Client error',
response.status,
errorData
);
}
// Handle server errors (5xx)
if (response.status >= 500) {
if (attempt < maxRetries - 1) {
await new Promise(resolve =>
setTimeout(resolve, retryDelay * (attempt + 1))
);
continue;
} else {
const errorData = response.headers.get('content-type')?.includes('json')
? await response.json()
: {};
throw new APIError(
'Server error. Please try again later.',
response.status,
errorData
);
}
}
// Success
return await response.json();
} catch (error) {
if (error instanceof APIError) {
throw error;
}
if (attempt < maxRetries - 1) {
await new Promise(resolve =>
setTimeout(resolve, retryDelay * (attempt + 1))
);
continue;
} else {
throw new APIError(`Request failed: ${error.message}`, 0, null);
}
}
}
}
// Usage
try {
const result = await makeRequestWithRetry(
'https://api.spreetaileu.com/api/api/v1/outbound',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(orderData)
}
);
console.log('Order created successfully:', result);
} catch (error) {
if (error instanceof APIError) {
if (error.statusCode === 409) {
console.log(`Duplicate order: ${error.message}`);
} else if (error.statusCode === 400) {
console.log(`Validation error: ${error.message}`);
} else if (error.statusCode === 401) {
console.log('Authentication failed - refreshing token...');
// Refresh token logic here
} else {
console.log(`Error (${error.statusCode}): ${error.message}`);
}
} else {
console.log('Unexpected error:', error);
}
}Retry Strategies
Exponential Backoff
import time
import random
def exponential_backoff(attempt: int, base_delay: float = 1.0, max_delay: float = 60.0) -> float:
"""Calculate exponential backoff delay with jitter"""
delay = min(base_delay * (2 ** attempt), max_delay)
jitter = random.uniform(0, delay * 0.1) # Add 10% jitter
return delay + jitter
# Usage in retry loop
for attempt in range(max_retries):
try:
# Make request
break
except APIError as e:
if e.status_code >= 500 and attempt < max_retries - 1:
delay = exponential_backoff(attempt)
time.sleep(delay)
continue
raiseValidation Before Requests
Prevent errors by validating data before sending:
from datetime import datetime
import re
def validate_order(order_data: Dict) -> tuple[bool, Optional[str]]:
"""Validate order data before sending to API"""
# Required fields
required_fields = [
'ReferenceNumber', 'ReceivedDate', 'DispatchBy',
'Status', 'Currency', 'PaymentStatus', 'ChannelBuyerName',
'OrderItems', 'DeliveryAddress', 'BillingAddress'
]
for field in required_fields:
if field not in order_data:
return False, f"{field} is required"
# Validate date formats
date_fields = ['ReceivedDate', 'DispatchBy']
for field in date_fields:
try:
datetime.fromisoformat(order_data[field].replace('Z', '+00:00'))
except (ValueError, AttributeError):
return False, f"{field} must be in ISO 8601 format"
# Validate currency code
valid_currencies = ['EUR', 'GBP', 'USD']
if order_data['Currency'] not in valid_currencies:
return False, f"Currency must be one of: {', '.join(valid_currencies)}"
# Validate order items
if not order_data['OrderItems'] or len(order_data['OrderItems']) == 0:
return False, "OrderItems must contain at least one item"
for item in order_data['OrderItems']:
if 'SKU' not in item or 'Qty' not in item:
return False, "Each OrderItem must have SKU and Qty"
if item['Qty'] <= 0:
return False, "OrderItem Qty must be greater than 0"
return True, None
# Usage
is_valid, error = validate_order(order_data)
if not is_valid:
print(f"Validation failed: {error}")
else:
# Proceed with API call
result = create_order(order_data)Best Practices
- Always Check Response Status: Don't assume requests succeed
- Implement Retry Logic: For transient errors (5xx, 429)
- Validate Before Sending: Catch validation errors early
- Log Errors: Log errors with context for debugging
- Handle Specific Errors: Different error types need different handling
- Respect Rate Limits: Implement backoff for 429 errors
- Refresh Tokens: Automatically refresh expired tokens
Support
For persistent errors or questions:
- Email: [email protected]
- Include: Error message, status code, request details, and timestamp
Updated 1 day ago
