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​