API Rate Limiting
> **Skill Purpose:** API rate limiting implementation, throttling strategies, and abuse prevention patterns
SKILL.md
Full skill instructions
API Rate Limiting
Skill Purpose: API rate limiting implementation, throttling strategies, and abuse prevention patterns
Core Skill Pattern
Objective: Establish comprehensive API rate limiting patterns with configurable throttling, user-based limits, and abuse prevention.
Universal Pattern:
- Define rate limiting strategies and algorithms
- Create configurable limit rules and policies
- Set up user-based and endpoint-specific limiting
- Establish monitoring and alerting procedures
- Create rate limiting bypass and exception handling
Key Decisions (Project-Specific):
- Rate limiting algorithm and strategy
- Limit granularity (global, user, endpoint)
- Storage mechanism for limit tracking
- Monitoring and alerting requirements
- Bypass policies and exception handling
Project-Specific Implementation Notes
Customize per project:
- Rate limiting algorithm based on traffic patterns and requirements
- Limit granularity based on API usage and business needs
- Storage approach based on performance and scalability requirements
- Monitoring depth based on service criticality
- Bypass policies based on operational requirements
Example Implementation (API Rate Limiting Pattern)
Note: This is an example pattern for API rate limiting. Adapt rate limiting algorithms and storage based on your specific API requirements and infrastructure.
Prerequisites (Example)
- API endpoints identified and categorized
- Rate limiting requirements documented
- Storage mechanism selected (Redis, database, memory)
- Monitoring and alerting systems established
Example: API Rate Limiting Implementation
Framework-Specific Example: This demonstrates rate limiting patterns using token bucket algorithm. Adapt for your rate limiting algorithm and storage requirements.
1. Rate Limiting Algorithms
// Rate limiting algorithms
interface RateLimitAlgorithm {
name: string;
checkLimit(key: string, limit: number, window: number): Promise<RateLimitResult>;
resetLimit(key: string): Promise<void>;
}
interface RateLimitResult {
allowed: boolean;
remaining: number;
resetTime: number;
retryAfter?: number;
}
// Token Bucket Algorithm
class TokenBucketAlgorithm implements RateLimitAlgorithm {
name = 'token_bucket';
constructor(private storage: RateLimitStorage) {}
async checkLimit(key: string, limit: number, window: number): Promise<RateLimitResult> {
const bucket = await this.storage.getBucket(key);
const now = Date.now();
// Initialize bucket if not exists
if (!bucket) {
await this.storage.setBucket(key, {
tokens: limit,
lastRefill: now,
limit,
window
});
return {
allowed: true,
remaining: limit - 1,
resetTime: now + window
};
}
// Refill tokens based on time elapsed
const timeElapsed = now - bucket.lastRefill;
const tokensToAdd = Math.floor((timeElapsed / window) * limit);
const currentTokens = Math.min(bucket.tokens + tokensToAdd, limit);
// Check if token available
if (currentTokens >= 1) {
// Consume token
const updatedBucket = {
...bucket,
tokens: currentTokens - 1,
lastRefill: now
};
await this.storage.setBucket(key, updatedBucket);
return {
allowed: true,
remaining: currentTokens - 1,
resetTime: now + window
};
} else {
// Calculate retry after time
const timeToNextToken = Math.ceil((window / limit) * (1 - currentTokens));
return {
allowed: false,
remaining: 0,
resetTime: bucket.lastRefill + window,
retryAfter: timeToNextToken
};
}
}
async resetLimit(key: string): Promise<void> {
await this.storage.deleteBucket(key);
}
}
// Sliding Window Algorithm
class SlidingWindowAlgorithm implements RateLimitAlgorithm {
name = 'sliding_window';
constructor(private storage: RateLimitStorage) {}
async checkLimit(key: string, limit: number, window: number): Promise<RateLimitResult> {
const now = Date.now();
const windowStart = now - window;
// Get existing requests in window
const requests = await this.storage.getRequests(key, windowStart);
if (requests.length < limit) {
// Add current request
await this.storage.addRequest(key, now);
return {
allowed: true,
remaining: limit - requests.length - 1,
resetTime: now + window
};
} else {
// Find oldest request to calculate retry after
const oldestRequest = Math.min(...requests);
const retryAfter = Math.ceil((oldestRequest + window - now) / 1000);
return {
allowed: false,
remaining: 0,
resetTime: oldestRequest + window,
retryAfter
};
}
}
async resetLimit(key: string): Promise<void> {
await this.storage.deleteRequests(key);
}
}
// Fixed Window Counter Algorithm
class FixedWindowAlgorithm implements RateLimitAlgorithm {
name = 'fixed_window';
constructor(private storage: RateLimitStorage) {}
async checkLimit(key: string, limit: number, window: number): Promise<RateLimitResult> {
const now = Date.now();
const currentWindow = Math.floor(now / window) * window;
const nextWindow = currentWindow + window;
// Get current window count
const count = await this.storage.getWindowCount(key, currentWindow);
if (count < limit) {
// Increment count
await this.storage.incrementWindowCount(key, currentWindow);
return {
allowed: true,
remaining: limit - count - 1,
resetTime: nextWindow
};
} else {
return {
allowed: false,
remaining: 0,
resetTime: nextWindow,
retryAfter: Math.ceil((nextWindow - now) / 1000)
};
}
}
async resetLimit(key: string): Promise<void> {
await this.storage.deleteWindowCount(key);
}
}
2. Rate Limiting Configuration
// Rate limiting configuration and rules
interface RateLimitRule {
name: string;
algorithm: string;
limit: number;
window: number; // in milliseconds
scope: 'global' | 'user' | 'ip' | 'endpoint' | 'custom';
keyGenerator?: (context: RateLimitContext) => string;
bypass?: (context: RateLimitContext) => boolean;
responseHeaders?: boolean;
penalty?: PenaltyConfig;
}
interface PenaltyConfig {
enabled: boolean;
multiplier: number;
duration: number;
maxPenalty: number;
}
interface RateLimitContext {
requestId: string;
userId?: string;
ipAddress?: string;
endpoint?: string;
method?: string;
userAgent?: string;
timestamp: number;
metadata?: Record<string, any>;
}
class RateLimitConfig {
private rules: Map<string, RateLimitRule> = new Map();
private globalDefaults: Partial<RateLimitRule> = {
algorithm: 'token_bucket',
limit: 100,
window: 60000, // 1 minute
scope: 'user',
responseHeaders: true
};
addRule(rule: RateLimitRule): void {
this.rules.set(rule.name, rule);
}
getRule(name: string): RateLimitRule {
const rule = this.rules.get(name);
if (!rule) {
throw new Error(`Rate limit rule ${name} not found`);
}
return { ...this.globalDefaults, ...rule };
}
listRules(): RateLimitRule[] {
return Array.from(this.rules.values());
}
removeRule(name: string): void {
this.rules.delete(name);
}
// Predefined common rules
static createCommonRules(): RateLimitConfig {
const config = new RateLimitConfig();
// Global API limit
config.addRule({
name: 'global_api',
algorithm: 'token_bucket',
limit: 1000,
window: 60000, // 1000 requests per minute
scope: 'global',
responseHeaders: true
});
// User-based limit
config.addRule({
name: 'user_requests',
algorithm: 'sliding_window',
limit: 100,
window: 60000, // 100 requests per minute per user
scope: 'user',
keyGenerator: (context) => context.userId || `anonymous_${context.ipAddress}`,
responseHeaders: true
});
// IP-based limit
config.addRule({
name: 'ip_requests',
algorithm: 'fixed_window',
limit: 200,
window: 60000, // 200 requests per minute per IP
scope: 'ip',
keyGenerator: (context) => context.ipAddress || 'unknown',
responseHeaders: true
});
// Endpoint-specific limits
config.addRule({
name: 'auth_endpoints',
algorithm: 'token_bucket',
limit: 10,
window: 60000, // 10 auth requests per minute
scope: 'endpoint',
keyGenerator: (context) => context.endpoint || 'unknown',
bypass: (context) => context.endpoint?.startsWith('/health'),
responseHeaders: true
});
// Sensitive operations
config.addRule({
name: 'sensitive_operations',
algorithm: 'sliding_window',
limit: 5,
window: 300000, // 5 requests per 5 minutes
scope: 'user',
keyGenerator: (context) => context.userId || `anonymous_${context.ipAddress}`,
penalty: {
enabled: true,
multiplier: 2,
duration: 600000, // 10 minutes
maxPenalty: 60 // Maximum 1 hour penalty
}
});
return config;
}
}
3. Rate Limiting Middleware
// Rate limiting middleware implementation
class RateLimitMiddleware {
private algorithms: Map<string, RateLimitAlgorithm> = new Map();
private config: RateLimitConfig;
private storage: RateLimitStorage;
constructor(config: RateLimitConfig, storage: RateLimitStorage) {
this.config = config;
this.storage = storage;
// Register algorithms
this.algorithms.set('token_bucket', new TokenBucketAlgorithm(storage));
this.algorithms.set('sliding_window', new SlidingWindowAlgorithm(storage));
this.algorithms.set('fixed_window', new FixedWindowAlgorithm(storage));
}
async checkRateLimit(ruleName: string, context: RateLimitContext): Promise<RateLimitResult> {
const rule = this.config.getRule(ruleName);
const algorithm = this.algorithms.get(rule.algorithm);
if (!algorithm) {
throw new Error(`Rate limiting algorithm ${rule.algorithm} not found`);
}
// Check bypass conditions
if (rule.bypass && rule.bypass(context)) {
return {
allowed: true,
remaining: Number.MAX_SAFE_INTEGER,
resetTime: Date.now() + 86400000 // 24 hours from now
};
}
// Generate limit key
const key = this.generateLimitKey(rule, context);
// Check limit
const result = await algorithm.checkLimit(key, rule.limit, rule.window);
// Apply penalty if needed
if (!result.allowed && rule.penalty?.enabled) {
await this.applyPenalty(key, rule.penalty, context);
}
return result;
}
async checkMultipleLimits(ruleNames: string[], context: RateLimitContext): Promise<RateLimitCheckResult> {
const results: Map<string, RateLimitResult> = new Map();
let overallAllowed = true;
let mostRestrictive: RateLimitResult | null = null;
for (const ruleName of ruleNames) {
try {
const result = await this.checkRateLimit(ruleName, context);
results.set(ruleName, result);
if (!result.allowed) {
overallAllowed = false;
// Track most restrictive limit
if (!mostRestrictive || result.retryAfter! > mostRestrictive.retryAfter!) {
mostRestrictive = result;
}
}
} catch (error) {
console.error(`Error checking rate limit for rule ${ruleName}:`, error);
// Fail open - allow request but log error
results.set(ruleName, {
allowed: true,
remaining: Number.MAX_SAFE_INTEGER,
resetTime: Date.now() + 86400000
});
}
}
return {
allowed: overallAllowed,
results: Object.fromEntries(results),
mostRestrictive: mostRestrictive || undefined
};
}
private generateLimitKey(rule: RateLimitRule, context: RateLimitContext): string {
if (rule.keyGenerator) {
return rule.keyGenerator(context);
}
switch (rule.scope) {
case 'global':
return 'global';
case 'user':
return context.userId || `anonymous_${context.ipAddress}`;
case 'ip':
return context.ipAddress || 'unknown';
case 'endpoint':
return context.endpoint || 'unknown';
case 'custom':
return `${context.requestId}_${context.timestamp}`;
default:
return 'default';
}
}
private async applyPenalty(key: string, penalty: PenaltyConfig, context: RateLimitContext): Promise<void> {
// Get current penalty count
const penaltyKey = `${key}_penalty`;
const currentPenalty = await this.storage.getPenalty(penaltyKey) || 0;
// Calculate new penalty
const newPenalty = Math.min(currentPenalty + 1, penalty.maxPenalty);
// Apply penalty duration
const penaltyDuration = penalty.duration * Math.pow(penalty.multiplier, newPenalty - 1);
// Store penalty
await this.storage.setPenalty(penaltyKey, {
count: newPenalty,
expiresAt: Date.now() + penaltyDuration,
reason: 'rate_limit_violation',
context
});
console.log(`Applied rate limit penalty to ${key}: ${newPenalty} violations, duration: ${penaltyDuration}ms`);
}
// Express.js middleware example
middleware(ruleNames: string[], options: MiddlewareOptions = {}) {
return async (req: any, res: any, next: any) => {
try {
const context: RateLimitContext = {
requestId: req.id || req.headers['x-request-id'] || this.generateRequestId(),
userId: req.user?.id,
ipAddress: req.ip || req.connection.remoteAddress,
endpoint: req.route?.path || req.path,
method: req.method,
userAgent: req.headers['user-agent'],
timestamp: Date.now(),
metadata: {
headers: req.headers,
query: req.query
}
};
const result = await this.checkMultipleLimits(ruleNames, context);
if (!result.allowed) {
const response: RateLimitResponse = {
error: 'Rate limit exceeded',
message: options.message || 'Too many requests',
retryAfter: result.mostRestrictive?.retryAfter,
resetTime: result.mostRestrictive?.resetTime,
limits: result.results
};
// Set rate limit headers
if (options.includeHeaders !== false) {
res.set('X-RateLimit-Limit', this.getTotalLimit(result.results));
res.set('X-RateLimit-Remaining', this.getTotalRemaining(result.results));
res.set('X-RateLimit-Reset', Math.ceil((result.mostRestrictive?.resetTime || 0) / 1000));
if (result.mostRestrictive?.retryAfter) {
res.set('Retry-After', result.mostRestrictive.retryAfter);
}
}
return res.status(429).json(response);
}
// Add rate limit info to request for downstream use
req.rateLimit = {
checked: true,
results: result.results,
remaining: this.getTotalRemaining(result.results)
};
next();
} catch (error) {
console.error('Rate limiting middleware error:', error);
// Fail open - allow request but log error
next();
}
};
}
private generateRequestId(): string {
return `req_${Date.now()}_${Math.random().toString(36).substring(7)}`;
}
private getTotalLimit(results: Record<string, RateLimitResult>): number {
return Object.values(results).reduce((sum, result) => sum + (result.remaining + 1), 0);
}
private getTotalRemaining(results: Record<string, RateLimitResult>): number {
return Object.values(results).reduce((sum, result) => sum + result.remaining, 0);
}
}
interface RateLimitCheckResult {
allowed: boolean;
results: Record<string, RateLimitResult>;
mostRestrictive?: RateLimitResult;
}
interface MiddlewareOptions {
message?: string;
includeHeaders?: boolean;
}
interface RateLimitResponse {
error: string;
message: string;
retryAfter?: number;
resetTime?: number;
limits: Record<string, RateLimitResult>;
}
4. Rate Limiting Storage
// Rate limiting storage abstraction
interface RateLimitStorage {
// Token bucket operations
getBucket(key: string): Promise<TokenBucket | null>;
setBucket(key: string, bucket: TokenBucket): Promise<void>;
deleteBucket(key: string): Promise<void>;
// Sliding window operations
getRequests(key: string, since: number): Promise<number[]>;
addRequest(key: string, timestamp: number): Promise<void>;
deleteRequests(key: string): Promise<void>;
// Fixed window operations
getWindowCount(key: string, window: number): Promise<number>;
incrementWindowCount(key: string, window: number): Promise<void>;
deleteWindowCount(key: string): Promise<void>;
// Penalty operations
getPenalty(key: string): Promise<number | null>;
setPenalty(key: string, penalty: PenaltyInfo): Promise<void>;
deletePenalty(key: string): Promise<void>;
}
interface TokenBucket {
tokens: number;
lastRefill: number;
limit: number;
window: number;
}
interface PenaltyInfo {
count: number;
expiresAt: number;
reason: string;
context: RateLimitContext;
}
// Redis implementation
class RedisRateLimitStorage implements RateLimitStorage {
constructor(private redis: any) {}
async getBucket(key: string): Promise<TokenBucket | null> {
const data = await this.redis.get(`bucket:${key}`);
return data ? JSON.parse(data) : null;
}
async setBucket(key: string, bucket: TokenBucket): Promise<void> {
await this.redis.setex(`bucket:${key}`, Math.ceil(bucket.window / 1000), JSON.stringify(bucket));
}
async deleteBucket(key: string): Promise<void> {
await this.redis.del(`bucket:${key}`);
}
async getRequests(key: string, since: number): Promise<number[]> {
const requests = await this.redis.zrangebyscore(`requests:${key}`, since, '+inf');
return requests.map(Number);
}
async addRequest(key: string, timestamp: number): Promise<void> {
await this.redis.zadd(`requests:${key}`, timestamp, timestamp);
await this.redis.expire(`requests:${key}`, 3600); // 1 hour expiry
}
async deleteRequests(key: string): Promise<void> {
await this.redis.del(`requests:${key}`);
}
async getWindowCount(key: string, window: number): Promise<number> {
const count = await this.redis.get(`window:${key}:${window}`);
return count ? parseInt(count) : 0;
}
async incrementWindowCount(key: string, window: number): Promise<void> {
const count = await this.redis.incr(`window:${key}:${window}`);
await this.redis.expire(`window:${key}:${window}`, Math.ceil(window / 1000) + 60);
}
async deleteWindowCount(key: string): Promise<void> {
await this.redis.del(`window:${key}:*`);
}
async getPenalty(key: string): Promise<number | null> {
const data = await this.redis.get(`penalty:${key}`);
return data ? JSON.parse(data).count : null;
}
async setPenalty(key: string, penalty: PenaltyInfo): Promise<void> {
await this.redis.setex(`penalty:${key}`, Math.ceil((penalty.expiresAt - Date.now()) / 1000), JSON.stringify(penalty));
}
async deletePenalty(key: string): Promise<void> {
await this.redis.del(`penalty:${key}`);
}
}
// Memory implementation (for development/testing)
class MemoryRateLimitStorage implements RateLimitStorage {
private buckets = new Map<string, TokenBucket>();
private requests = new Map<string, number[]>();
private windows = new Map<string, number>();
private penalties = new Map<string, PenaltyInfo>();
async getBucket(key: string): Promise<TokenBucket | null> {
return this.buckets.get(key) || null;
}
async setBucket(key: string, bucket: TokenBucket): Promise<void> {
this.buckets.set(key, bucket);
}
async deleteBucket(key: string): Promise<void> {
this.buckets.delete(key);
}
async getRequests(key: string, since: number): Promise<number[]> {
const requests = this.requests.get(key) || [];
return requests.filter(timestamp => timestamp >= since);
}
async addRequest(key: string, timestamp: number): Promise<void> {
const requests = this.requests.get(key) || [];
requests.push(timestamp);
this.requests.set(key, requests);
}
async deleteRequests(key: string): Promise<void> {
this.requests.delete(key);
}
async getWindowCount(key: string, window: number): Promise<number> {
return this.windows.get(`${key}:${window}`) || 0;
}
async incrementWindowCount(key: string, window: number): Promise<void> {
const current = this.windows.get(`${key}:${window}`) || 0;
this.windows.set(`${key}:${window}`, current + 1);
}
async deleteWindowCount(key: string): Promise<void> {
// Delete all windows for this key
for (const [windowKey] of this.windows) {
if (windowKey.startsWith(`${key}:`)) {
this.windows.delete(windowKey);
}
}
}
async getPenalty(key: string): Promise<number | null> {
const penalty = this.penalties.get(key);
return penalty ? penalty.count : null;
}
async setPenalty(key: string, penalty: PenaltyInfo): Promise<void> {
this.penalties.set(key, penalty);
}
async deletePenalty(key: string): Promise<void> {
this.penalties.delete(key);
}
}
5. Rate Limiting Monitoring
// Rate limiting monitoring and alerting
interface RateLimitMetrics {
totalRequests: number;
blockedRequests: number;
blockedByRule: Record<string, number>;
averageResponseTime: number;
topViolators: ViolatorInfo[];
}
interface ViolatorInfo {
key: string;
violations: number;
lastViolation: number;
rule: string;
}
class RateLimitMonitor {
private metrics: RateLimitMetrics = {
totalRequests: 0,
blockedRequests: 0,
blockedByRule: {},
averageResponseTime: 0,
topViolators: []
};
private responseTimes: number[] = [];
private violators = new Map<string, ViolatorInfo>();
recordRequest(blocked: boolean, rule?: string, key?: string, responseTime?: number): void {
this.metrics.totalRequests++;
if (blocked) {
this.metrics.blockedRequests++;
if (rule) {
this.metrics.blockedByRule[rule] = (this.metrics.blockedByRule[rule] || 0) + 1;
}
if (key) {
this.updateViolator(key, rule);
}
}
if (responseTime) {
this.responseTimes.push(responseTime);
if (this.responseTimes.length > 1000) {
this.responseTimes.shift();
}
this.metrics.averageResponseTime = this.responseTimes.reduce((sum, time) => sum + time, 0) / this.responseTimes.length;
}
}
private updateViolator(key: string, rule: string): void {
const existing = this.violators.get(key);
if (existing) {
existing.violations++;
existing.lastViolation = Date.now();
} else {
this.violators.set(key, {
key,
violations: 1,
lastViolation: Date.now(),
rule
});
}
// Update top violators
this.metrics.topViolators = Array.from(this.violators.values())
.sort((a, b) => b.violations - a.violations)
.slice(0, 10);
}
getMetrics(): RateLimitMetrics {
return { ...this.metrics };
}
getAlerts(): RateLimitAlert[] {
const alerts: RateLimitAlert[] = [];
// High block rate alert
const blockRate = this.metrics.blockedRequests / this.metrics.totalRequests;
if (blockRate > 0.1) { // More than 10% blocked
alerts.push({
type: 'high_block_rate',
severity: 'warning',
message: `High block rate: ${(blockRate * 100).toFixed(2)}%`,
value: blockRate,
threshold: 0.1
});
}
// Top violator alert
const topViolator = this.metrics.topViolators[0];
if (topViolator && topViolator.violations > 100) {
alerts.push({
type: 'excessive_violations',
severity: 'warning',
message: `Excessive violations from ${topViolator.key}: ${topViolator.violations}`,
value: topViolator.violations,
threshold: 100
});
}
// Response time alert
if (this.metrics.averageResponseTime > 100) { // More than 100ms
alerts.push({
type: 'high_response_time',
severity: 'warning',
message: `High average response time: ${this.metrics.averageResponseTime.toFixed(2)}ms`,
value: this.metrics.averageResponseTime,
threshold: 100
});
}
return alerts;
}
reset(): void {
this.metrics = {
totalRequests: 0,
blockedRequests: 0,
blockedByRule: {},
averageResponseTime: 0,
topViolators: []
};
this.responseTimes = [];
this.violators.clear();
}
}
interface RateLimitAlert {
type: string;
severity: 'info' | 'warning' | 'error';
message: string;
value: number;
threshold: number;
}
Integration Patterns
API Department Integration
- Endpoint protection and throttling
- User-based rate limiting
- API abuse prevention
Security Department Integration
- DDoS protection coordination
- Abuse detection and response
- Security monitoring integration
Monitoring Integration
- Rate limiting metrics collection
- Alert generation and notification
- Performance monitoring
Stop Conditions
STOP rate limiting operations and escalate if:
- Rate limiting system failures
- Storage mechanism failures
- High false positive rates
- Performance degradation
- Legitimate users blocked
PAUSE operations if:
- Rate limiting configuration changes
- Storage system maintenance
- High traffic periods
- Monitoring system issues
Best Practices
Rate Limiting Strategy:
- Multiple algorithms for different use cases
- Hierarchical limiting (global → user → endpoint)
- Graceful degradation and fail-open
- Clear communication to users
Configuration Management:
- Environment-specific limits
- Dynamic limit adjustment
- A/B testing of limits
- Business rule alignment
Monitoring and Alerting:
- Comprehensive metrics collection
- Real-time alerting
- Performance impact monitoring
- User experience tracking
