Error Handling Guide
This guide covers error handling, rate limiting, and best practices for building resilient integrations with Assist Insurances APIs.
HTTP Status Codes
All APIs use standard HTTP status codes to indicate success or failure:
| Code | Status | Description |
|---|---|---|
| 200 | OK | Request succeeded |
| 400 | Bad Request | Invalid request parameters or format |
| 401 | Unauthorized | Missing or invalid authentication token |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource not found |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server error (retry recommended) |
| 503 | Service Unavailable | Temporary service outage (retry recommended) |
Error Response Format
All error responses follow a consistent JSON format:
{
"statusCode": 401,
"message": "Unauthorized. Access token is missing or invalid.",
"error": "Unauthorized"
}
Fields:
statusCode- HTTP status code (number)message- Human-readable error descriptionerror- Error type/category (optional)
Common Errors
400 Bad Request
Indicates invalid request parameters, malformed JSON/XML, or missing required fields.
Example:
{
"statusCode": 400,
"message": "Invalid request: Missing required field 'in_customer.dateOfBirth'"
}
Common Causes:
- Missing required fields
- Invalid date formats
- Malformed JSON or XML
- Invalid enum values
- Data validation failures
How to Fix:
- Validate request data before sending
- Check required fields against documentation
- Ensure correct date/time formats (ISO 8601)
- Validate XML against schema
Example Fix:
- JavaScript
- Python
function validateQuoteData(data) {
const required = [
'in_policy.dateInception',
'in_customer.dateOfBirth',
'in_dts.raterCover'
];
const missing = required.filter(field => !data[field]);
if (missing.length > 0) {
throw new Error(`Missing required fields: ${missing.join(', ')}`);
}
// Validate date format
if (!/^\d{4}-\d{2}-\d{2}$/.test(data['in_customer.dateOfBirth'])) {
throw new Error('Invalid date format. Use YYYY-MM-DD');
}
// Validate enum
const validCovers = ['Comprehensive', 'ThirdParty', 'ThirdPartyFireAndTheft'];
if (!validCovers.includes(data['in_dts.raterCover'])) {
throw new Error(`Invalid cover type. Must be one of: ${validCovers.join(', ')}`);
}
return true;
}
from datetime import datetime
from typing import Dict, List
def validate_quote_data(data: Dict) -> bool:
"""Validate quote request data before sending."""
required = [
'in_policy.dateInception',
'in_customer.dateOfBirth',
'in_dts.raterCover'
]
missing = [field for field in required if field not in data]
if missing:
raise ValueError(f"Missing required fields: {', '.join(missing)}")
# Validate date format
try:
datetime.strptime(data['in_customer.dateOfBirth'], '%Y-%m-%d')
except ValueError:
raise ValueError('Invalid date format. Use YYYY-MM-DD')
# Validate enum
valid_covers = ['Comprehensive', 'ThirdParty', 'ThirdPartyFireAndTheft']
if data['in_dts.raterCover'] not in valid_covers:
raise ValueError(f"Invalid cover type. Must be one of: {', '.join(valid_covers)}")
return True
401 Unauthorized
Indicates missing, expired, or invalid authentication token.
Example:
{
"statusCode": 401,
"message": "Unauthorized. Access token is missing or invalid."
}
Common Causes:
- Token not included in request
- Token has expired (> 1 hour old)
- Invalid or malformed token
- Incorrect client credentials
How to Fix:
- Ensure
Authorization: Bearer {token}header is present - Check token expiry and refresh if needed
- Verify client credentials are correct
- Request new token if current one is invalid
Example Implementation:
- JavaScript
- Python
class APIClient {
async makeRequest(endpoint, options = {}) {
let response = await this.sendRequest(endpoint, options);
// Handle token expiry
if (response.status === 401) {
console.log('Token expired, refreshing...');
this.token = null; // Clear cached token
const newToken = await this.getAccessToken();
// Retry with new token
response = await this.sendRequest(endpoint, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${newToken}`,
},
});
}
return response;
}
async sendRequest(endpoint, options) {
const token = await this.getAccessToken();
return fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`,
},
});
}
}
import requests
from typing import Dict, Optional
class APIClient:
def make_request(self, endpoint: str, **kwargs) -> requests.Response:
"""Make request with automatic token refresh on 401."""
response = self._send_request(endpoint, **kwargs)
# Handle token expiry
if response.status_code == 401:
print('Token expired, refreshing...')
self.token = None # Clear cached token
new_token = self.get_access_token()
# Retry with new token
headers = kwargs.get('headers', {})
headers['Authorization'] = f'Bearer {new_token}'
kwargs['headers'] = headers
response = self._send_request(endpoint, **kwargs)
return response
def _send_request(self, endpoint: str, **kwargs) -> requests.Response:
"""Internal method to send request."""
token = self.get_access_token()
headers = kwargs.get('headers', {})
headers['Authorization'] = f'Bearer {token}'
kwargs['headers'] = headers
return self.session.request(url=f'{self.base_url}{endpoint}', **kwargs)
403 Forbidden
Indicates insufficient permissions for the requested operation.
Example:
{
"statusCode": 403,
"message": "Insufficient permissions for override operation"
}
Common Causes:
- Client doesn't have required role/permission
- Operation not allowed for your account type
- Resource access restricted
How to Fix:
- Contact support to request additional permissions
- Verify your client has the correct roles assigned
- Use a different operation within your permissions
404 Not Found
Indicates the requested resource doesn't exist.
Example:
{
"statusCode": 404,
"message": "Quote QT-2025-001234 not found"
}
Common Causes:
- Invalid resource ID
- Resource has been deleted
- Typo in URL or ID
- Looking up non-existent vehicle/eircode
How to Fix:
- Verify the resource ID is correct
- Check for typos in the request URL
- Ensure the resource hasn't been deleted
- For lookups, handle gracefully (not all VRMs/Eircodes exist)
Example:
- JavaScript
- Python
async function lookupVehicleSafe(vrm) {
try {
return await client.lookupVehicle(vrm);
} catch (error) {
if (error.status === 404) {
console.log(`Vehicle ${vrm} not found in database`);
return null; // Return null instead of throwing
}
throw error; // Re-throw other errors
}
}
// Usage
const vehicle = await lookupVehicleSafe('12-D-12345');
if (vehicle) {
console.log('Vehicle found:', vehicle);
} else {
console.log('Vehicle not in database, manual entry required');
}
from typing import Optional, Dict
def lookup_vehicle_safe(vrm: str) -> Optional[Dict]:
"""Look up vehicle, returning None if not found."""
try:
return client.lookup_vehicle(vrm)
except requests.HTTPError as e:
if e.response.status_code == 404:
print(f'Vehicle {vrm} not found in database')
return None
raise # Re-raise other errors
# Usage
vehicle = lookup_vehicle_safe('12-D-12345')
if vehicle:
print(f'Vehicle found: {vehicle}')
else:
print('Vehicle not in database, manual entry required')
429 Rate Limit Exceeded
Indicates you've exceeded the rate limit of 100 requests per minute.
Example:
{
"statusCode": 429,
"message": "Rate limit exceeded"
}
Rate Limit Headers: All responses include rate limit information:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1634567890
How to Fix:
- Implement exponential backoff
- Cache tokens (don't request new token for every API call)
- Batch requests where possible
- Monitor rate limit headers
Exponential Backoff Implementation:
- JavaScript
- Python
async function makeRequestWithRetry(requestFn, maxRetries = 5) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await requestFn();
if (response.status === 429) {
if (attempt === maxRetries) {
throw new Error('Rate limit exceeded - max retries reached');
}
// Exponential backoff: 1s, 2s, 4s, 8s, 16s
const delay = Math.pow(2, attempt) * 1000;
console.log(`Rate limited. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
if (!response.ok) {
throw new Error(`Request failed: ${response.statusText}`);
}
return response;
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
console.log(`Attempt ${attempt + 1} failed. Retrying...`);
}
}
}
// Usage
const response = await makeRequestWithRetry(async () => {
return fetch('https://api.assistinsurances.ie/priceQuote', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(quoteData),
});
});
import time
from typing import Callable
def make_request_with_retry(
request_fn: Callable,
max_retries: int = 5
) -> requests.Response:
"""Make request with exponential backoff retry."""
for attempt in range(max_retries + 1):
try:
response = request_fn()
if response.status_code == 429:
if attempt == max_retries:
raise Exception('Rate limit exceeded - max retries reached')
# Exponential backoff: 1s, 2s, 4s, 8s, 16s
delay = (2 ** attempt)
print(f'Rate limited. Retrying in {delay}s...')
time.sleep(delay)
continue
response.raise_for_status()
return response
except requests.HTTPError as e:
if attempt == max_retries:
raise
print(f'Attempt {attempt + 1} failed. Retrying...')
# Usage
response = make_request_with_retry(lambda: requests.post(
'https://api.assistinsurances.ie/priceQuote',
headers={
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json',
},
json=quote_data
))
500 Internal Server Error
Indicates an unexpected server error. These are usually transient.
Example:
{
"statusCode": 500,
"message": "Internal server error"
}
How to Fix:
- Retry the request (implement retry logic)
- If persists, contact support
- Log the error details for troubleshooting
503 Service Unavailable
Indicates temporary service outage, usually during maintenance.
Example:
{
"statusCode": 503,
"message": "Service temporarily unavailable"
}
How to Fix:
- Implement retry logic with exponential backoff
- Check service status page
- Queue requests if possible
Complete Error Handling Pattern
Here's a complete, production-ready error handling implementation:
- JavaScript
- Python
class RobustAPIClient {
constructor(clientId, clientSecret, baseUrl) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.baseUrl = baseUrl;
this.token = null;
this.tokenExpiresAt = null;
}
async makeRequest(endpoint, options = {}, maxRetries = 3) {
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const token = await this.getAccessToken();
const response = await fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`,
},
});
// Handle rate limiting
if (response.status === 429) {
if (attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
console.warn(`Rate limited. Retrying in ${delay}ms...`);
await this.sleep(delay);
continue;
}
throw new Error('Rate limit exceeded - max retries reached');
}
// Handle token expiry
if (response.status === 401) {
if (attempt === 0) {
console.log('Token expired, refreshing...');
this.token = null;
continue; // Retry with new token
}
throw new Error('Authentication failed');
}
// Handle server errors with retry
if (response.status >= 500) {
if (attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
console.warn(`Server error. Retrying in ${delay}ms...`);
await this.sleep(delay);
continue;
}
throw new Error(`Server error: ${response.statusText}`);
}
// Handle client errors (don't retry)
if (response.status >= 400) {
const error = await response.json();
throw new Error(
`API error (${error.statusCode}): ${error.message}`
);
}
// Success
return response.json();
} catch (error) {
lastError = error;
// Don't retry client errors (4xx except 401, 429)
if (error.message.includes('API error')) {
const statusCode = parseInt(error.message.match(/\((\d+)\)/)?.[1]);
if (statusCode >= 400 && statusCode < 500 &&
statusCode !== 401 && statusCode !== 429) {
throw error;
}
}
if (attempt === maxRetries) {
throw lastError;
}
console.log(`Attempt ${attempt + 1} failed. Retrying...`);
}
}
throw lastError;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async getAccessToken() {
// Token caching implementation (see Authentication guide)
// ...
}
}
import time
import requests
from typing import Dict, Any, Optional
from datetime import datetime, timedelta
class RobustAPIClient:
def __init__(self, client_id: str, client_secret: str, base_url: str):
self.client_id = client_id
self.client_secret = client_secret
self.base_url = base_url
self.token: Optional[str] = None
self.token_expires_at: Optional[datetime] = None
self.session = requests.Session()
def make_request(
self,
endpoint: str,
max_retries: int = 3,
**kwargs
) -> Dict[str, Any]:
"""Make API request with comprehensive error handling."""
last_error = None
for attempt in range(max_retries + 1):
try:
token = self.get_access_token()
headers = kwargs.get('headers', {})
headers['Authorization'] = f'Bearer {token}'
kwargs['headers'] = headers
response = self.session.request(
url=f'{self.base_url}{endpoint}',
**kwargs
)
# Handle rate limiting
if response.status_code == 429:
if attempt < max_retries:
delay = 2 ** attempt
print(f'Rate limited. Retrying in {delay}s...')
time.sleep(delay)
continue
raise Exception('Rate limit exceeded - max retries reached')
# Handle token expiry
if response.status_code == 401:
if attempt == 0:
print('Token expired, refreshing...')
self.token = None
continue # Retry with new token
raise Exception('Authentication failed')
# Handle server errors with retry
if response.status_code >= 500:
if attempt < max_retries:
delay = 2 ** attempt
print(f'Server error. Retrying in {delay}s...')
time.sleep(delay)
continue
raise Exception(f'Server error: {response.text}')
# Handle client errors (don't retry)
if response.status_code >= 400:
error = response.json()
raise Exception(
f"API error ({error['statusCode']}): {error['message']}"
)
# Success
return response.json()
except Exception as e:
last_error = e
# Don't retry client errors (4xx except 401, 429)
if 'API error' in str(e):
status_code = int(str(e).split('(')[1].split(')')[0])
if 400 <= status_code < 500 and \
status_code not in [401, 429]:
raise
if attempt == max_retries:
raise last_error
print(f'Attempt {attempt + 1} failed. Retrying...')
raise last_error
def get_access_token(self) -> str:
# Token caching implementation (see Authentication guide)
# ...
pass
Logging and Monitoring
What to Log
Always Log:
- Request timestamp
- Endpoint called
- HTTP status code
- Response time
- Error messages
Never Log:
- Access tokens
- Client secrets
- Full request/response bodies (may contain PII)
Example Logging:
function logAPICall(endpoint, method, statusCode, duration, error = null) {
const logEntry = {
timestamp: new Date().toISOString(),
endpoint,
method,
statusCode,
duration: `${duration}ms`,
success: statusCode >= 200 && statusCode < 300,
};
if (error) {
logEntry.error = error.message;
}
console.log(JSON.stringify(logEntry));
}
// Usage
const startTime = Date.now();
try {
const response = await makeRequest('/priceQuote', { method: 'POST' });
logAPICall('/priceQuote', 'POST', 200, Date.now() - startTime);
} catch (error) {
logAPICall('/priceQuote', 'POST', error.status, Date.now() - startTime, error);
}
Best Practices Summary
- Always implement retry logic for transient failures (429, 500, 503)
- Use exponential backoff to avoid overwhelming the server
- Cache tokens - don't request new token for every API call
- Handle 401 gracefully - automatic token refresh
- Don't retry 4xx errors (except 401, 429) - they won't succeed
- Log errors but never log sensitive data
- Set timeouts on all HTTP requests (recommended: 30 seconds)
- Monitor rate limits using response headers
- Validate input before sending to API
- Handle 404 gracefully for lookup endpoints
Monitoring Checklist
- Error rate monitoring (alert if > 5%)
- Response time monitoring (alert if > 5s)
- Rate limit monitoring (alert if frequently hitting limit)
- Token refresh monitoring (alert on auth failures)
- 5xx error monitoring (alert on server errors)
- Logging of all API interactions
- Alerting for sustained failures
Support
If you encounter persistent errors:
- Check this documentation for solutions
- Review your logs for patterns
- Verify your credentials and permissions
- Contact technical support: neil.reilly@assistinsurances.ie
When contacting support, provide:
- Timestamp of the error
- Endpoint called
- HTTP status code
- Error message
- Your client ID (never send client secret)
- Request ID if available
Next Steps
- Authentication Guide - Implement OAuth 2.0
- Insly Integration - Insly-specific patterns
- Applied Systems Integration - Applied Systems patterns
- API Reference - Full API documentation