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.