Rate Limiting
Azotte implements comprehensive rate limiting to ensure fair usage, system stability, and optimal performance across all API endpoints and integrations.
Rate Limit Overview
Default Limits
- API Calls: 1,000 requests per minute per API key
- Webhook Delivery: 10,000 events per minute per tenant
- Bulk Operations: 100 operations per minute
- File Uploads: 50 uploads per minute per tenant
Limit Headers
Every API response includes rate limit information:
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200
X-RateLimit-Retry-After: 60
Rate Limit Types
Per-API Key Limits
interface ApiKeyLimits {
requests_per_minute: number;
requests_per_hour: number;
requests_per_day: number;
burst_allowance: number;
concurrent_requests: number;
}
// Example limits by API key type
const limits = {
test_key: {
requests_per_minute: 100,
requests_per_hour: 1000,
burst_allowance: 10
},
live_key: {
requests_per_minute: 1000,
requests_per_hour: 50000,
burst_allowance: 50
},
enterprise_key: {
requests_per_minute: 5000,
requests_per_hour: 250000,
burst_allowance: 200
}
};
Per-Endpoint Limits
Different endpoints have specific rate limits based on their computational cost:
| Endpoint Category | Requests/Min | Burst | Notes |
|---|---|---|---|
| Customer Management | 500 | 50 | Standard CRUD operations |
| Subscription Creation | 100 | 10 | Complex business logic |
| Payment Processing | 200 | 20 | High-security operations |
| Bulk Operations | 50 | 5 | Resource-intensive |
| Reports/Analytics | 100 | 10 | Data-heavy responses |
Per-Tenant Limits
{
"tenant_id": "tenant_123",
"limits": {
"api_requests": {
"per_minute": 2000,
"per_hour": 100000,
"per_day": 2000000
},
"webhook_events": {
"per_minute": 10000,
"per_hour": 500000
},
"data_export": {
"per_day": 10,
"max_records_per_export": 100000
}
}
}
Implementation Strategies
Token Bucket Algorithm
class TokenBucket {
private tokens: number;
private lastRefill: number;
private readonly capacity: number;
private readonly refillRate: number; // tokens per second
constructor(capacity: number, refillRate: number) {
this.capacity = capacity;
this.refillRate = refillRate;
this.tokens = capacity;
this.lastRefill = Date.now();
}
consume(tokensRequested: number = 1): boolean {
this.refill();
if (this.tokens >= tokensRequested) {
this.tokens -= tokensRequested;
return true;
}
return false;
}
private refill(): void {
const now = Date.now();
const timePassed = (now - this.lastRefill) / 1000;
const tokensToAdd = Math.floor(timePassed * this.refillRate);
this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
this.lastRefill = now;
}
}
Sliding Window
class SlidingWindowRateLimit {
private requests: Map<string, number[]> = new Map();
private readonly windowSize: number; // milliseconds
private readonly maxRequests: number;
constructor(windowSize: number, maxRequests: number) {
this.windowSize = windowSize;
this.maxRequests = maxRequests;
}
isAllowed(key: string): boolean {
const now = Date.now();
const windowStart = now - this.windowSize;
if (!this.requests.has(key)) {
this.requests.set(key, []);
}
const timestamps = this.requests.get(key)!;
// Remove old timestamps
while (timestamps.length > 0 && timestamps[0] < windowStart) {
timestamps.shift();
}
if (timestamps.length < this.maxRequests) {
timestamps.push(now);
return true;
}
return false;
}
}
Client-Side Handling
Exponential Backoff
class ApiClient {
async makeRequest(url, options, retryCount = 0) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('X-RateLimit-Retry-After');
const delayMs = retryAfter ? parseInt(retryAfter) * 1000 :
Math.pow(2, retryCount) * 1000;
if (retryCount < 3) {
await this.delay(delayMs);
return this.makeRequest(url, options, retryCount + 1);
}
throw new Error('Rate limit exceeded, max retries reached');
}
return response;
} catch (error) {
throw error;
}
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
Rate Limit Monitoring
class RateLimitMonitor {
constructor() {
this.limits = new Map();
}
updateLimits(response) {
const limit = response.headers.get('X-RateLimit-Limit');
const remaining = response.headers.get('X-RateLimit-Remaining');
const reset = response.headers.get('X-RateLimit-Reset');
this.limits.set('current', {
limit: parseInt(limit),
remaining: parseInt(remaining),
reset: parseInt(reset),
resetDate: new Date(parseInt(reset) * 1000)
});
// Warn when approaching limits
if (remaining < limit * 0.1) {
console.warn('Approaching rate limit:', remaining, 'requests remaining');
}
}
getCurrentLimits() {
return this.limits.get('current');
}
timeUntilReset() {
const limits = this.getCurrentLimits();
return limits ? limits.resetDate - new Date() : 0;
}
}
SDK Integration
import { Azotte } from '@azotte/sdk';
const azotte = new Azotte({
apiKey: 'sk_live_...',
rateLimitOptions: {
retryOnRateLimit: true,
maxRetries: 3,
retryDelay: 'exponential', // or 'linear' or number
respectRetryAfter: true
}
});
// Automatic rate limit handling
const subscription = await azotte.subscriptions.create(data);
Enterprise Features
Custom Rate Limits
{
"tenant_id": "enterprise_tenant",
"custom_limits": {
"api_calls": {
"per_second": 100,
"per_minute": 5000,
"per_hour": 250000
},
"priority_queue": true,
"dedicated_capacity": true,
"custom_endpoints": {
"/bulk-import": {
"per_minute": 10,
"requires_approval": true
}
}
}
}
Priority Queuing
interface PriorityRequest {
request_id: string;
priority: 'low' | 'medium' | 'high' | 'critical';
tenant_id: string;
endpoint: string;
created_at: Date;
}
class PriorityQueue {
private queues = {
critical: [],
high: [],
medium: [],
low: []
};
enqueue(request: PriorityRequest): void {
this.queues[request.priority].push(request);
}
dequeue(): PriorityRequest | null {
for (const priority of ['critical', 'high', 'medium', 'low']) {
if (this.queues[priority].length > 0) {
return this.queues[priority].shift();
}
}
return null;
}
}
Monitoring & Analytics
Rate Limit Metrics
interface RateLimitMetrics {
tenant_id: string;
period: string;
metrics: {
total_requests: number;
blocked_requests: number;
success_rate: number;
average_response_time: number;
peak_requests_per_second: number;
};
top_endpoints: {
endpoint: string;
request_count: number;
block_count: number;
}[];
}
Real-Time Dashboard
Rate Limit Dashboard
┌─────────────────────────────────────────────────────────┐
│ Tenant: enterprise_123 │
├─────────────────────────────────────────────────────────┤
│ Current Usage: 1,245 / 5,000 req/min (24.9%) │
│ Peak Today: 4,892 req/min at 14:23 │
│ Blocked Requests: 23 (0.5%) │
│ Next Reset: 00:42 seconds │
└─────────────────────────────────────────────────────────┘
Top Endpoints by Usage:
• /api/subscriptions/create 45%
• /api/customers/list 23%
• /api/payments/process 18%
• /api/webhooks/events 14%
Best Practices
Efficient API Usage
// Batch operations when possible
const customers = await azotte.customers.createBatch([
{ name: 'Customer 1', email: 'c1@example.com' },
{ name: 'Customer 2', email: 'c2@example.com' }
]);
// Use pagination for large datasets
const subscriptions = await azotte.subscriptions.list({
limit: 100,
starting_after: 'sub_last_id'
});
// Implement caching
const cachedCustomer = cache.get(`customer_${id}`);
if (!cachedCustomer) {
const customer = await azotte.customers.retrieve(id);
cache.set(`customer_${id}`, customer, 300); // 5 min cache
}
Request Optimization
// Use conditional requests
const response = await fetch('/api/customers', {
headers: {
'If-None-Match': lastEtag,
'If-Modified-Since': lastModified
}
});
if (response.status === 304) {
// Use cached data
return cachedData;
}
// Request only needed fields
const customer = await azotte.customers.retrieve(id, {
expand: ['subscriptions'],
fields: ['id', 'name', 'email', 'created_at']
});
Error Handling
Rate Limit Errors
try {
const result = await azotte.subscriptions.create(data);
} catch (error) {
if (error.type === 'rate_limit_error') {
const retryAfter = error.retry_after;
console.log(`Rate limited. Retry after ${retryAfter} seconds`);
// Implement exponential backoff
await new Promise(resolve =>
setTimeout(resolve, retryAfter * 1000)
);
// Retry the request
return azotte.subscriptions.create(data);
}
throw error;
}
Circuit Breaker Pattern
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureThreshold = threshold;
this.timeout = timeout;
this.failureCount = 0;
this.lastFailureTime = null;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
}
async call(fn) {
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 fn();
this.reset();
return result;
} catch (error) {
this.recordFailure();
throw error;
}
}
}
Configuration
Environment-Specific Limits
const rateLimitConfig = {
development: {
requests_per_minute: 100,
burst_allowance: 10
},
staging: {
requests_per_minute: 500,
burst_allowance: 25
},
production: {
requests_per_minute: 1000,
burst_allowance: 50
}
};
const config = rateLimitConfig[process.env.NODE_ENV];
Next Steps
- Learn about Error Handling
- Understand Tenant Context
- Explore Authentication