Error Handling
Overview
The Kira API uses conventional HTTP response codes to indicate the success or failure of requests. This guide covers common errors, their causes, and how to handle them in your integration.
HTTP Status Codes
| Status Code | Meaning | When It Occurs |
|---|---|---|
200 OK | Success | Request completed successfully |
201 Created | Created | Resource successfully created |
400 Bad Request | Client Error | Invalid request parameters or validation error |
401 Unauthorized | Auth Error | Missing or invalid authentication token |
404 Not Found | Not Found | Resource doesn't exist |
409 Conflict | Conflict | Idempotency key reused (user creation only) |
500 Internal Server Error | Server Error | Unexpected server error |
Error Response Format
There are two error response formats depending on where the error occurs:
Business Logic Errors
Most API errors follow this simple format:
{
"code": "error_code",
"message": "Human-readable error description"
}Fields:
code- Machine-readable error code (e.g.,validation_error,unauthorized,not_found)message- Human-readable error message for logging or display
Request Schema Validation Errors
Errors from request validation return a structured format with details:
{
"error": "Invalid request data",
"details": [
{
"path": "field.name",
"message": "Error description",
"code": "error_type"
}
]
}Fields:
error- Always "Invalid request data" for schema validation errorsdetails- Array of specific validation errors with field path, message, and error code
Common Errors
Authentication Errors
Missing Authorization Token
Status: 401 Unauthorized
{
"code": "unauthorized",
"message": "No authorization token provided"
}Cause: Request missing Authorization header
Solution:
const headers = {
'Authorization': `Bearer ${accessToken}`, // Add this!
'Content-Type': 'application/json'
};Invalid or Expired Token
Status: 401 Unauthorized
{
"code": "unauthorized",
"message": "Token expired"
}Cause: JWT token has expired or is invalid
Solution: Re-authenticate to get a new token
async function requestWithAuth(url, options) {
try {
return await fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${accessToken}`,
...options.headers
}
});
} catch (error) {
if (error.status === 401) {
// Token expired, re-authenticate
accessToken = await authenticate();
// Retry request
return await fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${accessToken}`,
...options.headers
}
});
}
throw error;
}
}No Client ID in Claims
Status: 401 Unauthorized
{
"code": "unauthorized",
"message": "No client ID found in claims"
}Cause: JWT token doesn't contain required client ID claim
Solution: Ensure you're authenticating with valid credentials and API key
Validation Errors
There are two types of validation errors depending on where validation fails:
Schema Validation Errors (Request Format)
Status: 400 Bad Request
Format: Structured error with details array
{
"error": "Invalid request data",
"details": [
{
"path": "destination.currency",
"message": "Required",
"code": "invalid_type"
}
]
}Cause: Request body doesn't match the expected schema (missing fields, wrong types, invalid formats)
Solution: Validate request structure before sending
Business Logic Validation Errors
Status: 400 Bad Request
Format: Simple error with code and message
{
"code": "validation_error",
"message": "Customer not found: 550e8400-e29b-41d4-a716-446655440000"
}Cause: Request is well-formed but violates business rules
Solution: Ensure business logic requirements are met
Examples:
// Validate request structure before sending
function validateUserData(userData) {
const errors = [];
if (!userData.type) errors.push('type is required');
if (!userData.email) errors.push('email is required');
if (!userData.first_name) errors.push('first_name is required');
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(', ')}`);
}
}
// Check business logic requirements
async function createVirtualAccountSafe(userId, accountData) {
// Verify user exists and is verified first
const user = await getUser(userId);
if (!user) {
throw new Error('User not found');
}
if (user.verification_status !== 'verified') {
throw new Error('User must be verified before creating virtual accounts');
}
return await createVirtualAccount(userId, accountData);
}Resource Errors
Resource Not Found
Status: 404 Not Found
{
"code": "not_found",
"message": "User with ID 550e8400-e29b-41d4-a716-446655440000 not found"
}Cause: Resource doesn't exist or user doesn't have access
Solution: Verify resource ID and user permissions
async function getUser(userId) {
try {
const response = await fetch(`/v1/users/${userId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.status === 404) {
console.error(`User ${userId} not found`);
return null;
}
return await response.json();
} catch (error) {
console.error('Error fetching user:', error);
throw error;
}
}Idempotency Errors
Idempotency Key Missing
Status: 400 Bad Request
{
"code": "validation_error",
"message": "Idempotency key is required"
}Solution: Always include idempotency key for create operations
const idempotencyKey = `user-${Date.now()}-${Math.random()}`;
await fetch('/v1/users', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'idempotency-key': idempotencyKey // Always include!
},
body: JSON.stringify(userData)
});Idempotency Key Reused
User Creation:
Status: 409 Conflict
{
"code": "idempotency_key_reused",
"message": "Idempotency key has already been used with different request data"
}Virtual Account Creation:
Status: 400 Bad Request
{
"code": "validation_error",
"message": "Idempotency key has already been used with different request data"
}Cause: Same idempotency key used with different request body
Solution: Use unique keys for each request
async function createUserWithRetry(userData, maxRetries = 3) {
const idempotencyKey = `user-${userData.email}-${Date.now()}`;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await createUser(userData, idempotencyKey);
} catch (error) {
if (error.code === 'idempotency_key_reused') {
// Same key with same data - just return success
return await getUserByEmail(userData.email);
}
if (attempt === maxRetries - 1) throw error;
await sleep(1000 * (attempt + 1)); // Exponential backoff
}
}
}Business Logic Errors
User Not Verified
Status: 400 Bad Request
{
"code": "validation_error",
"message": "User must complete identity verification before creating virtual accounts"
}Solution: Check verification status before creating virtual accounts
async function createVirtualAccountSafe(userId, accountData) {
// Check user verification status
const user = await getUser(userId);
if (user.verification_status !== 'verified') {
console.error('User not verified. Verification required before creating virtual accounts.');
return {
success: false,
error: 'Please complete identity verification first'
};
}
// User is verified, proceed
return await createVirtualAccount(userId, accountData);
}Customer Not Found or Not Active
Customer Not Found:
Status: 400 Bad Request
{
"code": "validation_error",
"message": "Customer not found: 550e8400-e29b-41d4-a716-446655440000"
}Customer Not Active:
Status: 400 Bad Request
{
"code": "validation_error",
"message": "Customer is not active: 550e8400-e29b-41d4-a716-446655440000. Please activate the customer first."
}Solution: Ensure user has completed identity verification before creating virtual accounts
Cannot Update Verified User
Status: 400 Bad Request
{
"code": "validation_error",
"message": "Cannot update user information for verified users"
}Cause: Attempting to update personal information after verification
Solution: Only update allowed fields or create new verification
async function updateUser(userId, updates) {
const user = await getUser(userId);
if (user.verification_status === 'verified') {
// Filter out fields that can't be updated
const allowedFields = ['metadata', 'notification_preferences'];
const safeUpdates = Object.keys(updates)
.filter(key => allowedFields.includes(key))
.reduce((obj, key) => {
obj[key] = updates[key];
return obj;
}, {});
if (Object.keys(safeUpdates).length === 0) {
console.warn('No updates allowed for verified user');
return user;
}
return await patchUser(userId, safeUpdates);
}
// User not verified, can update everything
return await patchUser(userId, updates);
}Server Errors
Internal Server Error
Status: 500 Internal Server Error
{
"code": "internal_error",
"message": "An unexpected error occurred"
}Cause: Unexpected server error during request processing
Solution: Retry with exponential backoff, contact support if persists
async function requestWithRetry(requestFn, maxRetries = 3) {
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await requestFn();
} catch (error) {
lastError = error;
if (error.code === 'internal_error') {
const waitTime = Math.pow(2, attempt) * 2000; // 2s, 4s, 8s
console.log(`Server error. Retrying in ${waitTime}ms...`);
await sleep(waitTime);
continue;
}
// For other errors, don't retry
throw error;
}
}
// All retries failed
console.error('Failed after all retries:', lastError);
throw new Error('Service temporarily unavailable. Please try again later.');
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}Error Handling Best Practices
1. Implement Proper Error Handling
async function handleApiRequest(requestFn) {
try {
const response = await requestFn();
return { success: true, data: response };
} catch (error) {
console.error('API Error:', {
code: error.code,
message: error.message,
status: error.status
});
return {
success: false,
error: {
code: error.code,
message: error.message,
userMessage: getUserFriendlyMessage(error)
}
};
}
}
function getUserFriendlyMessage(error) {
const messages = {
'validation_error': 'Please check your input and try again.',
'unauthorized': 'Your session has expired. Please log in again.',
'not_found': 'The requested resource was not found.',
'idempotency_key_reused': 'This request was already processed with different data.',
'internal_error': 'An unexpected error occurred. Please try again later.'
};
return messages[error.code] || 'An error occurred. Please try again.';
}2. Log Errors for Debugging
function logError(context, error, additionalData = {}) {
console.error({
timestamp: new Date().toISOString(),
context,
errorCode: error.code,
errorMessage: error.message,
statusCode: error.status,
...additionalData
});
// Send to error tracking service
if (process.env.NODE_ENV === 'production') {
Sentry.captureException(error, {
extra: {
context,
...additionalData
}
});
}
}3. Validate Before Sending
function validateUserData(userData) {
const errors = [];
// Required fields
if (!userData.email) errors.push('Email is required');
if (!userData.phone_number) errors.push('Phone number is required');
// Format validation
if (userData.email && !isValidEmail(userData.email)) {
errors.push('Invalid email format');
}
if (userData.phone_number && !isValidE164(userData.phone_number)) {
errors.push('Phone number must be in E.164 format');
}
if (errors.length > 0) {
throw new ValidationError(errors.join(', '));
}
return true;
}
// Use before API call
try {
validateUserData(userData);
const user = await createUser(userData);
} catch (error) {
if (error instanceof ValidationError) {
// Handle validation error locally
console.error('Validation failed:', error.message);
}
}4. Implement Circuit Breaker
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureCount = 0;
this.threshold = threshold;
this.timeout = timeout;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextAttempt = Date.now();
}
async execute(request) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const response = await request();
this.onSuccess();
return response;
} 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;
}
}
}
// Usage
const breaker = new CircuitBreaker();
async function createVirtualAccountSafe(userId, data) {
try {
return await breaker.execute(() =>
createVirtualAccount(userId, data)
);
} catch (error) {
console.error('Circuit breaker triggered:', error.message);
throw error;
}
}Error Response Reference
Complete Error Code List
| Error Code | HTTP Status | Description |
|---|---|---|
validation_error | 400 | Invalid request data or business logic validation failure |
unauthorized | 401 | Missing or invalid authentication |
not_found | 404 | Resource not found |
idempotency_key_reused | 409 | Idempotency key used with different data (user creation only) |
internal_error | 500 | Unexpected server error |
Note: Virtual account creation returns validation_error (400) for idempotency key reuse instead of 409.
Debugging Tips
Enable Detailed Logging
const DEBUG = process.env.DEBUG === 'true';
async function apiCall(url, options) {
if (DEBUG) {
console.log('API Request:', {
url,
method: options.method,
headers: options.headers,
body: options.body
});
}
const response = await fetch(url, options);
if (DEBUG) {
console.log('API Response:', {
status: response.status,
headers: Object.fromEntries(response.headers.entries())
});
}
return response;
}Test Error Scenarios
// Test error handling in development
if (process.env.NODE_ENV === 'development') {
describe('Error Handling', () => {
it('handles 404 errors', async () => {
const result = await getUser('invalid-uuid');
expect(result).toBeNull();
});
it('handles validation errors', async () => {
await expect(createUser({})).rejects.toThrow('validation_error');
});
});
}Getting Help
If you encounter persistent errors:
- Review Documentation - Ensure you're following the guides correctly
- Search Error Code - Look up the specific error code in this guide
- Contact Support - Email [email protected] with:
- Error code and message
- Request ID (if available)
- Timestamp of the error
- Steps to reproduce
Next Steps
- Review all integration guides to ensure proper implementation
- Set up monitoring and alerting for errors
- Implement comprehensive error handling in your application
- Test error scenarios in sandbox environment
Updated 12 days ago
