Error Handling & Rate Limits
⚠️ Future Content: The comprehensive error handling documentation and rate limiting specifications below will be provided in future documentation updates. These detailed implementation guides are being developed for the API reference.
Learn how to handle API errors gracefully and work within Filedgr's rate limits to build robust, production-ready applications.
Error Types
HTTP Status Codes
Filedgr uses standard HTTP status codes to indicate the success or failure of API requests:
Success Codes
- 200 OK - Request succeeded
- 201 Created - Resource created successfully
- 202 Accepted - Request accepted for processing
- 204 No Content - Request succeeded, no content to return
Client Error Codes
- 400 Bad Request - Invalid request parameters
- 401 Unauthorized - Authentication required or invalid credentials
- 403 Forbidden - Insufficient permissions
- 404 Not Found - Resource doesn't exist
- 409 Conflict - Request conflicts with current resource state
- 422 Unprocessable Entity - Valid request but contains semantic errors
- 429 Too Many Requests - Rate limit exceeded
Server Error Codes
- 500 Internal Server Error - Server encountered an unexpected condition
- 502 Bad Gateway - Invalid response from upstream server
- 503 Service Unavailable - Service temporarily unavailable
- 504 Gateway Timeout - Upstream server timeout
Error Response Format
All API errors return a consistent JSON structure:
{
"error": {
"type": "validation_error",
"message": "Invalid request parameters",
"code": "INVALID_TEMPLATE_ID",
"statusCode": 400,
"requestId": "req_abc123",
"timestamp": "2024-01-15T10:30:00Z",
"details": [
{
"field": "templateId",
"message": "Template 'invalid-template' does not exist",
"code": "TEMPLATE_NOT_FOUND"
}
]
}
}
Common Error Types
Authentication Errors
{
"error": {
"type": "authentication_error",
"message": "Invalid API key",
"code": "INVALID_API_KEY",
"statusCode": 401
}
}
{
"error": {
"type": "authentication_error",
"message": "Access token has expired",
"code": "TOKEN_EXPIRED",
"statusCode": 401,
"details": {
"expiredAt": "2024-01-15T09:30:00Z"
}
}
}
Permission Errors
{
"error": {
"type": "permission_error",
"message": "Insufficient permissions to access this resource",
"code": "INSUFFICIENT_PERMISSIONS",
"statusCode": 403,
"details": {
"resource": "vault_abc123",
"requiredPermission": "VIEWER",
"currentPermission": "NONE"
}
}
}
Validation Errors
{
"error": {
"type": "validation_error",
"message": "Request validation failed",
"code": "VALIDATION_FAILED",
"statusCode": 422,
"details": [
{
"field": "name",
"message": "Name is required",
"code": "FIELD_REQUIRED"
},
{
"field": "templateId",
"message": "Template ID must be a valid format",
"code": "INVALID_FORMAT",
"pattern": "^[a-z0-9-]+$"
}
]
}
}
Rate Limit Errors
{
"error": {
"type": "rate_limit_error",
"message": "API rate limit exceeded",
"code": "RATE_LIMIT_EXCEEDED",
"statusCode": 429,
"details": {
"limit": 100,
"window": 3600,
"remaining": 0,
"resetAt": "2024-01-15T11:00:00Z",
"retryAfter": 300
}
}
}
Resource Errors
{
"error": {
"type": "resource_error",
"message": "Vault not found",
"code": "VAULT_NOT_FOUND",
"statusCode": 404,
"details": {
"vaultId": "vault_invalid123"
}
}
}
Error Handling Strategies
Basic Error Handling
async function createVault(vaultData) {
try {
const response = await fetch('https://api.filedgr.io/vaults', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(vaultData)
});
if (!response.ok) {
const errorData = await response.json();
throw new FiledgrError(errorData.error);
}
return await response.json();
} catch (error) {
console.error('Failed to create vault:', error);
throw error;
}
}
Custom Error Classes
class FiledgrError extends Error {
constructor(errorData) {
super(errorData.message);
this.name = 'FiledgrError';
this.type = errorData.type;
this.code = errorData.code;
this.statusCode = errorData.statusCode;
this.requestId = errorData.requestId;
this.details = errorData.details;
}
}
class RateLimitError extends FiledgrError {
constructor(errorData) {
super(errorData);
this.name = 'RateLimitError';
this.retryAfter = errorData.details?.retryAfter;
this.resetAt = errorData.details?.resetAt;
}
}
class ValidationError extends FiledgrError {
constructor(errorData) {
super(errorData);
this.name = 'ValidationError';
this.fieldErrors = errorData.details || [];
}
}
class AuthenticationError extends FiledgrError {
constructor(errorData) {
super(errorData);
this.name = 'AuthenticationError';
}
}
Comprehensive Error Handler
class FiledgrClient {
async makeRequest(endpoint, options = {}) {
try {
const response = await fetch(`https://api.filedgr.io${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
...options.headers
}
});
// Handle successful responses
if (response.ok) {
return await response.json();
}
// Handle error responses
const errorData = await response.json();
this.handleError(errorData.error, response.status);
} catch (error) {
if (error instanceof FiledgrError) {
throw error;
}
// Handle network errors
throw new Error(`Network error: ${error.message}`);
}
}
handleError(errorData, statusCode) {
switch (errorData.type) {
case 'rate_limit_error':
throw new RateLimitError(errorData);
case 'validation_error':
throw new ValidationError(errorData);
case 'authentication_error':
throw new AuthenticationError(errorData);
default:
throw new FiledgrError(errorData);
}
}
}
Error-Specific Handling
async function handleApiCall(apiCall) {
try {
return await apiCall();
} catch (error) {
switch (error.constructor) {
case RateLimitError:
console.log(`Rate limited. Retrying after ${error.retryAfter} seconds`);
await new Promise(resolve => setTimeout(resolve, error.retryAfter * 1000));
return await handleApiCall(apiCall); // Retry
case ValidationError:
console.log('Validation errors:');
error.fieldErrors.forEach(fieldError => {
console.log(`- ${fieldError.field}: ${fieldError.message}`);
});
throw error; // Re-throw for caller to handle
case AuthenticationError:
console.log('Authentication failed. Refreshing token...');
await this.refreshAccessToken();
return await handleApiCall(apiCall); // Retry with new token
default:
console.error(`API error: ${error.message} (${error.code})`);
throw error;
}
}
}
Retry Logic
Exponential Backoff
class RetryHandler {
constructor(maxRetries = 3, baseDelay = 1000) {
this.maxRetries = maxRetries;
this.baseDelay = baseDelay;
}
async executeWithRetry(operation, context = {}) {
let lastError;
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
// Don't retry certain error types
if (!this.shouldRetry(error, attempt)) {
throw error;
}
// Calculate delay with exponential backoff
const delay = this.calculateDelay(attempt, error);
console.log(`Attempt ${attempt + 1} failed. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
shouldRetry(error, attempt) {
// Don't retry if we've exceeded max attempts
if (attempt >= this.maxRetries) {
return false;
}
// Always retry rate limit errors
if (error instanceof RateLimitError) {
return true;
}
// Retry server errors (5xx)
if (error.statusCode >= 500) {
return true;
}
// Retry network timeouts
if (error.code === 'NETWORK_TIMEOUT') {
return true;
}
// Don't retry client errors (4xx) except rate limits
return false;
}
calculateDelay(attempt, error) {
// Use specific retry-after for rate limits
if (error instanceof RateLimitError && error.retryAfter) {
return error.retryAfter * 1000;
}
// Exponential backoff with jitter
const exponentialDelay = this.baseDelay * Math.pow(2, attempt);
const jitter = Math.random() * 1000; // Add randomness to prevent thundering herd
return exponentialDelay + jitter;
}
}
// Usage
const retryHandler = new RetryHandler();
const vault = await retryHandler.executeWithRetry(async () => {
return await filedgr.vaults.create(vaultData);
});
Circuit Breaker Pattern
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000, monitoringWindow = 120000) {
this.threshold = threshold;
this.timeout = timeout;
this.monitoringWindow = monitoringWindow;
this.failureCount = 0;
this.lastFailureTime = null;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
}
async execute(operation) {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime > this.timeout) {
this.state = 'HALF_OPEN';
} else {
throw new Error('Circuit breaker is OPEN');
}
}
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.failureCount >= this.threshold) {
this.state = 'OPEN';
}
}
}
Rate Limiting
Understanding Rate Limits
Filedgr implements rate limiting to ensure fair usage and system stability:
Default Limits
- API Calls: 1,000 requests per hour per API key
- File Uploads: 100 files per hour per vault
- Webhook Deliveries: 10,000 per hour per endpoint
- Data Stream Ingestion: 10,000 data points per hour per stream
Headers
Rate limit information is included in response headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1642262400
X-RateLimit-Window: 3600
Rate Limit Handling
class RateLimitManager {
constructor() {
this.limits = new Map();
}
updateLimits(headers) {
const limit = parseInt(headers['x-ratelimit-limit']);
const remaining = parseInt(headers['x-ratelimit-remaining']);
const reset = parseInt(headers['x-ratelimit-reset']);
this.limits.set('api', {
limit,
remaining,
reset: new Date(reset * 1000)
});
}
async waitIfNecessary() {
const apiLimits = this.limits.get('api');
if (!apiLimits) return;
// If we're close to the limit, wait
if (apiLimits.remaining < 10) {
const waitTime = apiLimits.reset.getTime() - Date.now();
if (waitTime > 0) {
console.log(`Rate limit nearly exceeded. Waiting ${waitTime}ms...`);
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}
}
getRemainingRequests() {
const apiLimits = this.limits.get('api');
return apiLimits?.remaining ?? Infinity;
}
}
// Enhanced client with rate limiting
class FiledgrClientWithRateLimit extends FiledgrClient {
constructor(config) {
super(config);
this.rateLimitManager = new RateLimitManager();
}
async makeRequest(endpoint, options = {}) {
// Check rate limits before making request
await this.rateLimitManager.waitIfNecessary();
const response = await super.makeRequest(endpoint, options);
// Update rate limit information
this.rateLimitManager.updateLimits(response.headers);
return response;
}
}
Bulk Operations and Rate Limits
class BulkOperationManager {
constructor(client, batchSize = 10, delayBetweenBatches = 1000) {
this.client = client;
this.batchSize = batchSize;
this.delayBetweenBatches = delayBetweenBatches;
}
async processBatch(items, operation) {
const results = [];
for (let i = 0; i < items.length; i += this.batchSize) {
const batch = items.slice(i, i + this.batchSize);
try {
// Process batch concurrently
const batchPromises = batch.map(item => operation(item));
const batchResults = await Promise.allSettled(batchPromises);
results.push(...batchResults);
// Delay between batches to respect rate limits
if (i + this.batchSize < items.length) {
await new Promise(resolve =>
setTimeout(resolve, this.delayBetweenBatches)
);
}
} catch (error) {
if (error instanceof RateLimitError) {
// Wait and retry the entire batch
await new Promise(resolve =>
setTimeout(resolve, error.retryAfter * 1000)
);
i -= this.batchSize; // Retry this batch
continue;
}
throw error;
}
}
return results;
}
}
// Usage
const bulkManager = new BulkOperationManager(filedgr, 5, 2000);
const uploadResults = await bulkManager.processBatch(files, async (file) => {
return await filedgr.vaults.uploadFile(vaultId, file);
});
Monitoring and Logging
Error Monitoring
class ErrorMonitor {
constructor() {
this.errorCounts = new Map();
this.errorHistory = [];
}
logError(error, context = {}) {
const errorKey = `${error.type}_${error.code}`;
// Update error counts
const currentCount = this.errorCounts.get(errorKey) || 0;
this.errorCounts.set(errorKey, currentCount + 1);
// Add to history
this.errorHistory.push({
timestamp: new Date().toISOString(),
error: {
type: error.type,
code: error.code,
message: error.message,
statusCode: error.statusCode
},
context,
requestId: error.requestId
});
// Alert on high error rates
this.checkErrorThresholds();
}
checkErrorThresholds() {
const recentErrors = this.errorHistory.filter(entry =>
Date.now() - new Date(entry.timestamp).getTime() < 300000 // Last 5 minutes
);
if (recentErrors.length > 10) {
console.warn(`High error rate detected: ${recentErrors.length} errors in 5 minutes`);
// Send alert to monitoring system
}
}
getErrorStats() {
return {
totalErrors: this.errorHistory.length,
errorCounts: Object.fromEntries(this.errorCounts),
recentErrors: this.errorHistory.slice(-10)
};
}
}
Request Logging
class RequestLogger {
constructor() {
this.requests = [];
}
logRequest(method, endpoint, options = {}, startTime) {
const endTime = Date.now();
const duration = endTime - startTime;
this.requests.push({
timestamp: new Date().toISOString(),
method,
endpoint,
duration,
success: !options.error,
error: options.error ? {
type: options.error.type,
code: options.error.code,
statusCode: options.error.statusCode
} : null,
rateLimitRemaining: options.rateLimitRemaining
});
// Log to console in development
if (process.env.NODE_ENV === 'development') {
const status = options.error ? '❌' : '✅';
console.log(`${status} ${method} ${endpoint} (${duration}ms)`);
if (options.error) {
console.log(` Error: ${options.error.message}`);
}
}
}
getPerformanceStats() {
const successfulRequests = this.requests.filter(r => r.success);
const failedRequests = this.requests.filter(r => !r.success);
return {
totalRequests: this.requests.length,
successRate: (successfulRequests.length / this.requests.length) * 100,
averageResponseTime: successfulRequests.reduce((sum, r) => sum + r.duration, 0) / successfulRequests.length,
errorRate: (failedRequests.length / this.requests.length) * 100,
commonErrors: this.getCommonErrors()
};
}
getCommonErrors() {
const errorCounts = {};
this.requests.forEach(request => {
if (request.error) {
const key = `${request.error.type}_${request.error.code}`;
errorCounts[key] = (errorCounts[key] || 0) + 1;
}
});
return Object.entries(errorCounts)
.sort(([,a], [,b]) => b - a)
.slice(0, 5);
}
}
Best Practices
Error Handling
- Always handle errors - Never ignore API errors
- Use appropriate error types - Handle different errors differently
- Provide context - Include relevant information in error messages
- Log for debugging - Maintain detailed error logs
- Fail gracefully - Provide fallback behavior when possible
Rate Limiting
- Monitor usage - Track API usage against limits
- Implement backoff - Use exponential backoff for rate limit errors
- Batch operations - Group multiple operations to reduce API calls
- Cache responses - Avoid unnecessary repeated requests
- Plan for growth - Consider rate limit upgrades as usage grows
Reliability
- Implement retries - Retry transient failures
- Use circuit breakers - Prevent cascading failures
- Timeout requests - Set reasonable request timeouts
- Monitor health - Track error rates and response times
- Test error scenarios - Test how your app handles various errors
Security
- Don't log sensitive data - Exclude credentials from logs
- Handle auth errors - Refresh tokens automatically
- Validate responses - Check response format and content
- Rate limit client-side - Respect limits to avoid blocking
Testing Error Scenarios
Unit Tests for Error Handling
describe('Error Handling', () => {
test('should handle rate limit errors', async () => {
const mockError = new RateLimitError({
type: 'rate_limit_error',
message: 'Rate limit exceeded',
code: 'RATE_LIMIT_EXCEEDED',
statusCode: 429,
details: { retryAfter: 60 }
});
jest.spyOn(filedgr.vaults, 'create').mockRejectedValue(mockError);
await expect(createVaultWithRetry(vaultData)).resolves.toBeDefined();
expect(filedgr.vaults.create).toHaveBeenCalledTimes(2); // Initial + retry
});
test('should not retry validation errors', async () => {
const mockError = new ValidationError({
type: 'validation_error',
message: 'Validation failed',
statusCode: 422,
details: [{ field: 'name', message: 'Name is required' }]
});
jest.spyOn(filedgr.vaults, 'create').mockRejectedValue(mockError);
await expect(createVaultWithRetry(vaultData)).rejects.toThrow(ValidationError);
expect(filedgr.vaults.create).toHaveBeenCalledTimes(1); // No retry
});
});
Integration Tests
// Test actual API error responses
describe('API Error Integration', () => {
test('should handle 404 errors', async () => {
try {
await filedgr.vaults.get('invalid_vault_id');
} catch (error) {
expect(error).toBeInstanceOf(FiledgrError);
expect(error.statusCode).toBe(404);
expect(error.code).toBe('VAULT_NOT_FOUND');
}
});
test('should handle authentication errors', async () => {
const clientWithBadAuth = new Filedgr({
apiKey: 'invalid_key',
apiSecret: 'invalid_secret'
});
try {
await clientWithBadAuth.vaults.list();
} catch (error) {
expect(error).toBeInstanceOf(AuthenticationError);
expect(error.statusCode).toBe(401);
}
});
});
Robust error handling and rate limit management are essential for production applications. Implement comprehensive error handling strategies to ensure your Filedgr integration remains reliable and user-friendly.