How to Configure Rate Limiting and Quota Enforcement per API Key in OmniRoute
OmniRoute provides a flexible rate limiting system that enforces per-API-key quotas through configurable algorithms, time windows, and multi-tier subscription limits as implemented in src/shared/utils/rateLimiter.ts.
The OmniRoute API gateway uses a centralized rate limiter utility to protect backend services from abuse while enabling tiered access control. This article explains how to configure and customize rate limiting behavior per API key based on the actual source code implementation.
Rate Limiter Architecture
The rate limiter in OmniRoute follows a modular design with three core components:
- Storage adapter – maintains request counters per key
- Algorithm engine – implements token bucket or sliding window logic
- Policy manager – maps API keys to quota configurations
All configuration resides in src/shared/utils/rateLimiter.ts, which exports the primary enforcement interface.
Configuring Rate Limits per API Key
Basic Quota Configuration
OmniRoute applies rate limits by associating API keys with quota profiles. The configuration structure follows this pattern:
// From src/shared/utils/rateLimiter.ts
interface QuotaConfig {
key: string; // API key identifier
algorithm: 'token_bucket' | 'sliding_window';
requestsPerWindow: number; // Maximum allowed requests
windowMs: number; // Time window in milliseconds
burstCapacity?: number; // Token bucket burst allowance
}
Configure a new API key with limits:
import { RateLimiter } from '@omniroute/shared/utils/rateLimiter';
const limiter = new RateLimiter({
storage: 'redis', // or 'memory' for single-node
defaultAlgorithm: 'token_bucket'
});
await limiter.registerKey({
key: 'pk_live_customer_001',
algorithm: 'sliding_window',
requestsPerWindow: 1000,
windowMs: 60000, // 1 minute window
});
Tiered Subscription Limits
OmniRoute supports multi-tier quotas through the subscription mapping system in rateLimiter.ts. The SubscriptionTier enum defines preset configurations:
// From src/shared/utils/rateLimiter.ts
enum SubscriptionTier {
FREE = 'free',
STARTER = 'starter',
PROFESSIONAL = 'professional',
ENTERPRISE = 'enterprise',
}
const TIER_DEFAULTS: Record<SubscriptionTier, QuotaConfig> = {
[SubscriptionTier.FREE]: {
algorithm: 'sliding_window',
requestsPerWindow: 100,
windowMs: 3600000, // 1 hour
},
[SubscriptionTier.ENTERPRISE]: {
algorithm: 'token_bucket',
requestsPerWindow: 100000,
burstCapacity: 5000,
windowMs: 60000, // 1 minute
},
// ... additional tiers
};
Apply tier-based limits:
await limiter.assignTier('pk_live_customer_002', SubscriptionTier.PROFESSIONAL);
Algorithm Selection and Behavior
Token Bucket Algorithm
The token bucket algorithm (default for high-volume keys) permits short bursts while maintaining long-term averages. Implementation from rateLimiter.ts:
// Core token bucket logic from src/shared/utils/rateLimiter.ts
private consumeToken(key: string, tokensRequested = 1): boolean {
const bucket = this.storage.get(key);
const now = Date.now();
const refillAmount = Math.floor(
(now - bucket.lastRefill) / this.config.refillRateMs
);
bucket.tokens = Math.min(
bucket.capacity,
bucket.tokens + refillAmount
);
bucket.lastRefill = now;
if (bucket.tokens >= tokensRequested) {
bucket.tokens -= tokensRequested;
this.storage.set(key, bucket);
return true; // Request allowed
}
return false; // Rate limit exceeded
}
Configure burst capacity for traffic spikes:
await limiter.registerKey({
key: 'pk_burst_heavy',
algorithm: 'token_bucket',
requestsPerWindow: 10000,
burstCapacity: 500, // Allow 500 requests instantly
windowMs: 60000,
});
Sliding Window Algorithm
The sliding window algorithm provides stricter enforcement without burst capability. Use for regulatory compliance or strict公平性 requirements:
await limiter.registerKey({
key: 'pk_strict_limited',
algorithm: 'sliding_window',
requestsPerWindow: 60, // Exactly 1 request per second average
windowMs: 60000,
});
Enforcement and Response Headers
OmniRoute automatically injects rate limit headers as implemented in the addHeaders method of rateLimiter.ts:
// Response header injection from src/shared/utils/rateLimiter.ts
private addHeaders(res: Response, key: string, quota: QuotaConfig): void {
const remaining = this.getRemainingRequests(key);
const resetTime = this.getWindowReset(key);
res.setHeader('X-RateLimit-Limit', quota.requestsPerWindow);
res.setHeader('X-RateLimit-Remaining', Math.max(0, remaining));
res.setHeader('X-RateLimit-Reset', resetTime);
if (remaining < 0) {
res.setHeader('Retry-After', Math.ceil((resetTime - Date.now()) / 1000));
res.status(429).json({ error: 'Rate limit exceeded' });
}
}
Clients receive standardized headers:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests in current window |
X-RateLimit-Remaining |
Requests remaining |
X-RateLimit-Reset |
Unix timestamp when window resets |
Retry-After |
Seconds to wait (on 429 response) |
Advanced Configuration Options
Custom Window Strategies
Override window calculation per key:
interface WindowStrategy {
fixed: boolean; // true = fixed windows, false = rolling
timezone?: string; // For daily/weekly resets
customAnchor?: number; // Custom window start timestamp
}
await limiter.registerKey({
key: 'pk_custom_window',
requestsPerWindow: 10000,
windowMs: 86400000, // 24 hours
windowStrategy: {
fixed: true,
timezone: 'America/New_York',
customAnchor: Date.UTC(2024, 0, 1), // Anchor to specific date
},
});
Dynamic Quota Adjustment
The RateLimiter class exposes runtime modification methods:
// Increase quota temporarily for flash sales or emergencies
await limiter.updateQuota('pk_live_customer_001', {
requestsPerWindow: 50000, // 5x normal limit
expiresAt: Date.now() + 3600000, // Revert after 1 hour
});
Storage Backend Configuration
Redis Cluster (Production)
const limiter = new RateLimiter({
storage: 'redis',
redis: {
cluster: [
{ host: 'redis-node-1', port: 6379 },
{ host: 'redis-node-2', port: 6379 },
],
keyPrefix: 'omniroute:rate:',
readReplicas: true,
},
syncIntervalMs: 100, // Cross-node synchronization frequency
});
In-Memory (Development/Single Node)
const limiter = new RateLimiter({
storage: 'memory',
cleanupIntervalMs: 60000, // Purge expired counters
});
Summary
- Per-API-key configuration uses the
registerKey()method withQuotaConfigobjects insrc/shared/utils/rateLimiter.ts. - Two algorithms are available: token bucket (with burst) and sliding window (strict).
- Tiered subscriptions map to preset configurations via
SubscriptionTierandassignTier(). - Response headers communicate limits to clients automatically.
- Storage backends include Redis cluster and in-memory options.
Frequently Asked Questions
How do I migrate existing API keys to new rate limits?
Use the updateQuota() method with the preserveUsage flag to transition without resetting counters. The source code in rateLimiter.ts handles atomic migration between configurations. Existing request counts carry forward proportionally based on window size changes.
Can different endpoints have different limits for the same API key?
Yes. Pass the scope parameter to checkLimit() for per-endpoint enforcement. The default scope is global ('*'), but you can namespace limits: await limiter.checkLimit(key, { scope: '/api/v1/search' }). Each scope maintains independent counters in storage.
What happens when Redis is unavailable?
The rate limiter fails open by default (requests allowed) with a fallbackMode: 'allow' configuration. Set fallbackMode: 'deny' to block traffic during storage outages. Memory-mode deployments bypass this concern but sacrifice horizontal scalability.
How do I monitor rate limit hit rates?
Enable the metricsCallback option to receive real-time consumption data. The callback receives (key: string, allowed: boolean, remaining: number) on every check. Export to Prometheus or Datadog for dashboard visualization.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →