Skip to main content

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 handling
  • message (string): Human-readable error description
  • details (array, optional): Field-level validation errors
  • requestId (string): Unique identifier for tracking this request
  • timestamp (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 succeeded
  • 201 Created - Resource created successfully
  • 202 Accepted - Request accepted for processing

4xx Client Errors

  • 400 Bad Request - Invalid request format or parameters
  • 401 Unauthorized - Invalid or missing authentication
  • 403 Forbidden - Insufficient permissions
  • 404 Not Found - Resource not found
  • 409 Conflict - Resource conflict (e.g., duplicate)
  • 422 Unprocessable Entity - Validation errors
  • 429 Too Many Requests - Rate limit exceeded

5xx Server Errors

  • 500 Internal Server Error - Unexpected server error
  • 502 Bad Gateway - Upstream service error
  • 503 Service Unavailable - Service temporarily unavailable
  • 504 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:

  1. Verify API key is correct
  2. Check environment (sandbox vs production)
  3. 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:

  1. Implement automatic token refresh
  2. Store tokens securely
  3. 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:

  1. Implement exponential backoff
  2. Optimize API usage patterns
  3. Consider API tier upgrade

Issue: Transaction Failures

Symptoms: Transactions failing with various error codes

Causes:

  • Insufficient funds
  • Invalid account information
  • AML/compliance issues

Solution:

  1. Validate account balances
  2. Verify account details
  3. 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.