# How to Configure Rate Limiting and Quota Enforcement per API Key in OmniRoute

> Learn to configure OmniRoute rate limiting and quota enforcement per API key. Discover flexible algorithms, time windows, and subscription limits in this technical guide.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-08-05

---

**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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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:

```typescript
// 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:

```typescript
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/rateLimiter.ts). The `SubscriptionTier` enum defines preset configurations:

```typescript
// 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:

```typescript
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/rateLimiter.ts):

```typescript
// 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:

```typescript
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:

```typescript
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/rateLimiter.ts):

```typescript
// 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:

```typescript
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:

```typescript
// 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)

```typescript
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)

```typescript
const limiter = new RateLimiter({
  storage: 'memory',
  cleanupIntervalMs: 60000,      // Purge expired counters
});

```

## Summary

- **Per-API-key configuration** uses the `registerKey()` method with `QuotaConfig` objects in [`src/shared/utils/rateLimiter.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/utils/rateLimiter.ts).
- **Two algorithms** are available: token bucket (with burst) and sliding window (strict).
- **Tiered subscriptions** map to preset configurations via `SubscriptionTier` and `assignTier()`.
- **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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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.