Skip to main content

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 CategoryRequests/MinBurstNotes
Customer Management50050Standard CRUD operations
Subscription Creation10010Complex business logic
Payment Processing20020High-security operations
Bulk Operations505Resource-intensive
Reports/Analytics10010Data-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