Error Handling
This guide covers error handling best practices and provides comprehensive information about error codes, troubleshooting steps, and recovery strategies for Rails Services APIs.
Error Response Format
All Rails Services APIs return errors in a consistent format:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description",
"details": [
{
"field": "fieldName",
"message": "Field-specific error message"
}
],
"requestId": "req_1234567890",
"timestamp": "2025-06-17T10:00:00Z"
}
}
Error Response Fields
code
(string): Machine-readable error code for programmatic handlingmessage
(string): Human-readable error descriptiondetails
(array, optional): Field-level validation errorsrequestId
(string): Unique identifier for tracking this requesttimestamp
(string): ISO 8601 timestamp when the error occurred
HTTP Status Codes
Rails Services uses standard HTTP status codes to indicate the success or failure of requests:
2xx Success
200 OK
- Request succeeded201 Created
- Resource created successfully202 Accepted
- Request accepted for processing
4xx Client Errors
400 Bad Request
- Invalid request format or parameters401 Unauthorized
- Invalid or missing authentication403 Forbidden
- Insufficient permissions404 Not Found
- Resource not found409 Conflict
- Resource conflict (e.g., duplicate)422 Unprocessable Entity
- Validation errors429 Too Many Requests
- Rate limit exceeded
5xx Server Errors
500 Internal Server Error
- Unexpected server error502 Bad Gateway
- Upstream service error503 Service Unavailable
- Service temporarily unavailable504 Gateway Timeout
- Request timeout
Common Error Codes
Authentication Errors
INVALID_API_KEY
HTTP Status: 401 Description: The provided API key is invalid or has been revoked.
{
"error": {
"code": "INVALID_API_KEY",
"message": "The API key provided is invalid"
}
}
Resolution:
- Verify your API key is correct
- Check if the API key has been revoked
- Ensure you're using the correct environment (sandbox vs production)
TOKEN_EXPIRED
HTTP Status: 401 Description: The JWT token has expired and needs to be refreshed.
{
"error": {
"code": "TOKEN_EXPIRED",
"message": "Access token has expired"
}
}
Resolution:
- Use your refresh token to obtain a new access token
- Implement automatic token renewal in your application
INSUFFICIENT_PERMISSIONS
HTTP Status: 403 Description: The authenticated user lacks permissions for this operation.
{
"error": {
"code": "INSUFFICIENT_PERMISSIONS",
"message": "User does not have permission to access this resource"
}
}
Resolution:
- Check user role assignments
- Contact your administrator to request additional permissions
Validation Errors
INVALID_REQUEST
HTTP Status: 400 Description: Request format or required parameters are invalid.
{
"error": {
"code": "INVALID_REQUEST",
"message": "Request validation failed",
"details": [
{
"field": "amount",
"message": "Amount must be a positive number"
},
{
"field": "currency",
"message": "Currency code is required"
}
]
}
}
Resolution:
- Review the field-level errors in the
details
array - Correct the invalid fields and retry the request
MISSING_REQUIRED_FIELD
HTTP Status: 422 Description: A required field is missing from the request.
{
"error": {
"code": "MISSING_REQUIRED_FIELD",
"message": "Required field is missing",
"details": [
{
"field": "participantId",
"message": "Participant ID is required"
}
]
}
}
Transaction Errors
INSUFFICIENT_FUNDS
HTTP Status: 422 Description: Insufficient balance to complete the transaction.
{
"error": {
"code": "INSUFFICIENT_FUNDS",
"message": "Insufficient funds in account",
"details": [
{
"field": "balance",
"message": "Account balance: $500.00, Required: $1000.00"
}
]
}
}
Resolution:
- Check account balance before initiating transactions
- Ensure sufficient funds are available
- Consider implementing balance checks in your application
TRANSACTION_LIMIT_EXCEEDED
HTTP Status: 422 Description: Transaction amount exceeds configured limits.
{
"error": {
"code": "TRANSACTION_LIMIT_EXCEEDED",
"message": "Transaction amount exceeds daily limit",
"details": [
{
"field": "amount",
"message": "Daily limit: $10,000.00, Attempted: $15,000.00"
}
]
}
}
INVALID_ACCOUNT
HTTP Status: 404 Description: The specified account is invalid or not found.
{
"error": {
"code": "INVALID_ACCOUNT",
"message": "Account not found or inactive"
}
}
DUPLICATE_TRANSACTION
HTTP Status: 409 Description: A transaction with the same reference already exists.
{
"error": {
"code": "DUPLICATE_TRANSACTION",
"message": "Transaction with this reference already exists",
"details": [
{
"field": "reference",
"message": "Reference 'TXN-2025-001' is already in use"
}
]
}
}
Resolution:
- Use unique transaction references
- Implement idempotency keys for safe retries
Rate Limiting Errors
RATE_LIMITED
HTTP Status: 429 Description: Too many requests have been made within the time window.
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded",
"details": [
{
"field": "retryAfter",
"message": "Retry after 60 seconds"
}
]
}
}
Resolution:
- Implement exponential backoff for retries
- Review rate limit headers in responses
- Consider upgrading your API tier for higher limits
Business Logic Errors
AML_CHECK_REQUIRED
HTTP Status: 422 Description: Transaction requires AML screening before processing.
{
"error": {
"code": "AML_CHECK_REQUIRED",
"message": "Transaction requires AML verification",
"details": [
{
"field": "amlId",
"message": "Submit AML request before proceeding"
}
]
}
}
MANUAL_REVIEW_REQUIRED
HTTP Status: 422 Description: Transaction flagged for manual review.
{
"error": {
"code": "MANUAL_REVIEW_REQUIRED",
"message": "Transaction requires manual review",
"details": [
{
"field": "reviewReason",
"message": "High-value transaction flagged for compliance review"
}
]
}
}
PARTICIPANT_NOT_APPROVED
HTTP Status: 403 Description: Participant account is not approved for transactions.
{
"error": {
"code": "PARTICIPANT_NOT_APPROVED",
"message": "Participant account pending approval"
}
}
System Errors
SERVICE_UNAVAILABLE
HTTP Status: 503 Description: Service is temporarily unavailable.
{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Service temporarily unavailable"
}
}
INTERNAL_ERROR
HTTP Status: 500 Description: An unexpected internal error occurred.
{
"error": {
"code": "INTERNAL_ERROR",
"message": "An internal error occurred",
"requestId": "req_1234567890"
}
}
Resolution:
- Retry the request after a brief delay
- Contact support if the error persists, including the
requestId
Error Handling Best Practices
1. Implement Proper Error Handling
Always handle errors gracefully in your application:
async function makeAPICall(url, options) {
try {
const response = await fetch(url, options);
const data = await response.json();
if (!response.ok) {
throw new APIError(data.error, response.status);
}
return data;
} catch (error) {
if (error instanceof APIError) {
handleAPIError(error);
} else {
handleNetworkError(error);
}
throw error;
}
}
class APIError extends Error {
constructor(errorData, statusCode) {
super(errorData.message);
this.code = errorData.code;
this.statusCode = statusCode;
this.details = errorData.details || [];
this.requestId = errorData.requestId;
}
}
2. Handle Different Error Types
Create specific handlers for different error categories:
function handleAPIError(error) {
switch (error.code) {
case 'INVALID_API_KEY':
case 'TOKEN_EXPIRED':
handleAuthenticationError(error);
break;
case 'INSUFFICIENT_FUNDS':
handleInsufficientFundsError(error);
break;
case 'RATE_LIMITED':
handleRateLimitError(error);
break;
case 'VALIDATION_ERROR':
handleValidationError(error);
break;
default:
handleGenericError(error);
}
}
function handleAuthenticationError(error) {
if (error.code === 'TOKEN_EXPIRED') {
// Refresh token and retry
refreshAccessToken().then(() => {
// Retry original request
});
} else {
// Redirect to login
window.location.href = '/login';
}
}
function handleRateLimitError(error) {
const retryAfter = error.details.find(d => d.field === 'retryAfter');
const delay = retryAfter ? parseInt(retryAfter.message) : 60;
// Implement exponential backoff
setTimeout(() => {
// Retry request
}, delay * 1000);
}
3. Implement Retry Logic
Use exponential backoff for retryable errors:
async function retryWithBackoff(fn, maxRetries = 3, baseDelay = 1000) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxRetries || !isRetryableError(error)) {
throw error;
}
const delay = baseDelay * Math.pow(2, attempt - 1);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
function isRetryableError(error) {
const retryableCodes = [
'RATE_LIMITED',
'SERVICE_UNAVAILABLE',
'INTERNAL_ERROR',
'TIMEOUT'
];
return retryableCodes.includes(error.code) ||
error.statusCode >= 500;
}
4. Log Errors for Monitoring
Implement comprehensive error logging:
function logError(error, context = {}) {
const logData = {
timestamp: new Date().toISOString(),
error: {
code: error.code,
message: error.message,
statusCode: error.statusCode,
requestId: error.requestId
},
context: context,
stackTrace: error.stack
};
// Send to logging service
logger.error('API Error', logData);
// Send to error tracking service
errorTracker.captureException(error, { extra: logData });
}
5. Validate Requests Before Sending
Prevent errors by validating requests client-side:
function validateTransactionRequest(request) {
const errors = [];
if (!request.amount || request.amount <= 0) {
errors.push({ field: 'amount', message: 'Amount must be positive' });
}
if (!request.currency || !['USD', 'EUR', 'GBP'].includes(request.currency)) {
errors.push({ field: 'currency', message: 'Invalid currency code' });
}
if (!request.participantBankId) {
errors.push({ field: 'participantBankId', message: 'Bank ID is required' });
}
if (errors.length > 0) {
throw new ValidationError('Request validation failed', errors);
}
}
Error Recovery Strategies
Idempotency
Use idempotency keys to safely retry requests:
async function createTransactionSafely(transactionData) {
const idempotencyKey = generateIdempotencyKey(transactionData);
const options = {
method: 'POST',
headers: {
'Authorization': `Bearer ${getAccessToken()}`,
'X-API-KEY': getAPIKey(),
'X-Idempotency-Key': idempotencyKey,
'Content-Type': 'application/json'
},
body: JSON.stringify(transactionData)
};
return retryWithBackoff(() =>
makeAPICall('/transactions', options)
);
}
function generateIdempotencyKey(data) {
// Create consistent key based on request data
const keyData = `${data.amount}-${data.currency}-${data.reference}-${Date.now()}`;
return crypto.createHash('sha256').update(keyData).digest('hex');
}
Circuit Breaker Pattern
Implement circuit breakers for resilience:
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.threshold = threshold;
this.timeout = timeout;
this.failureCount = 0;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextAttempt = Date.now();
}
async call(fn) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failureCount++;
if (this.failureCount >= this.threshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
}
}
}
Graceful Degradation
Implement fallbacks for non-critical features:
async function getTransactionHistory(participantId) {
try {
return await fetchFromAPI(`/transactions?participantId=${participantId}`);
} catch (error) {
if (error.code === 'SERVICE_UNAVAILABLE') {
// Fallback to cached data
return getCachedTransactionHistory(participantId);
}
throw error;
}
}
async function processPayment(paymentData) {
try {
return await createPayment(paymentData);
} catch (error) {
if (error.code === 'AML_CHECK_REQUIRED') {
// Queue for later processing
queuePaymentForAMLCheck(paymentData);
return { status: 'PENDING_AML_CHECK' };
}
throw error;
}
}
Monitoring and Alerting
Error Rate Monitoring
Track error rates and patterns:
class ErrorMonitor {
constructor() {
this.errorCounts = new Map();
this.errorRates = new Map();
}
recordError(errorCode, endpoint) {
const key = `${endpoint}:${errorCode}`;
const count = this.errorCounts.get(key) || 0;
this.errorCounts.set(key, count + 1);
// Calculate error rate
this.updateErrorRate(endpoint);
}
updateErrorRate(endpoint) {
const totalRequests = this.getTotalRequests(endpoint);
const totalErrors = this.getTotalErrors(endpoint);
const errorRate = totalErrors / totalRequests;
this.errorRates.set(endpoint, errorRate);
// Alert if error rate exceeds threshold
if (errorRate > 0.05) { // 5% error rate
this.sendAlert(endpoint, errorRate);
}
}
sendAlert(endpoint, errorRate) {
console.warn(`High error rate detected: ${endpoint} - ${(errorRate * 100).toFixed(2)}%`);
// Send to monitoring service
}
}
Health Checks
Implement health checks for proactive monitoring:
async function performHealthCheck() {
const checks = {
api: false,
identity: false,
transactions: false,
blockchain: false
};
try {
await fetch('https://api.ledgerlink.ai/v1/health');
checks.api = true;
} catch (error) {
console.error('API health check failed:', error);
}
try {
await fetch('https://identity.ledgerlink.ai/v1/health');
checks.identity = true;
} catch (error) {
console.error('Identity service health check failed:', error);
}
// Add more service checks...
return checks;
}
Troubleshooting Common Issues
Issue: "Invalid API Key" Error
Symptoms: 401 status with INVALID_API_KEY
error code
Causes:
- Incorrect API key
- API key revoked or expired
- Using sandbox key in production (or vice versa)
Solution:
- Verify API key is correct
- Check environment (sandbox vs production)
- Contact support to verify key status
Issue: Token Expiration Errors
Symptoms: Frequent 401 errors with TOKEN_EXPIRED
code
Causes:
- Not refreshing tokens properly
- Clock skew between systems
- Token storage issues
Solution:
- Implement automatic token refresh
- Store tokens securely
- Sync system clocks
Issue: Rate Limiting
Symptoms: 429 status with RATE_LIMITED
error code
Causes:
- Too many requests in short time
- Inefficient API usage
- Missing request optimization
Solution:
- Implement exponential backoff
- Optimize API usage patterns
- Consider API tier upgrade
Issue: Transaction Failures
Symptoms: Transactions failing with various error codes
Causes:
- Insufficient funds
- Invalid account information
- AML/compliance issues
Solution:
- Validate account balances
- Verify account details
- Implement AML screening
Getting Help
Support Channels
- Technical Support: Include error codes and request IDs
- Documentation: Refer to specific API documentation
- Status Page: Check for known issues
- Community Forum: Search for similar issues
Information to Include
When reporting errors, include:
- Error code and message
- Request ID (if available)
- HTTP status code
- Request payload (sanitized)
- Timestamp of occurrence
- Steps to reproduce
Proper error handling is crucial for building robust integrations with Rails Services. Follow these guidelines to create resilient applications that gracefully handle edge cases and provide excellent user experiences.