Dhal rate limits can be keyed by more than source IP. This is important for authenticated APIs, multi-tenant systems, and applications where many users share a network address.
Global rate limit
{
"rateLimit": {
"enabled": true,
"store": "memory",
"keyBy": ["ip", "route"],
"default": {
"windowSeconds": 60,
"max": 120
},
"routes": {
"/api/search": {
"windowSeconds": 60,
"max": 30
}
}
}
}Route-limit patterns support exact paths and * wildcards. The most specific matching pattern wins.
Identity keys
Rate limits support these key components:
iprouteuserIdtenantIdapiKeyId
Credential-stuffing controls additionally support userAgent.
{
"rateLimit": {
"keyBy": ["tenantId", "userId", "route"]
}
}Choose keys that match the abuse boundary. For example:
| Use case | Suggested key |
|---|---|
| Public unauthenticated endpoint | ip, route |
| Authenticated user action | userId, route |
| Tenant-wide quota | tenantId, route |
| API integration quota | apiKeyId, route |
| Login abuse | ip, route, optionally userAgent |
Identity headers
Dhal resolves identities from request properties first and configured headers second.
{
"identity": {
"headers": {
"userId": ["x-dhal-user-id", "x-user-id"],
"tenantId": ["x-dhal-tenant-id", "x-tenant-id"],
"apiKeyId": ["x-dhal-api-key-id", "x-api-key-id"]
}
}
}Only trust identity headers that are removed and rewritten by your authentication layer or trusted gateway. Never accept client-supplied identity headers unchanged from the public internet.
Route-level limits
{
"routes": {
"/api/export": {
"mode": "block",
"rateLimit": {
"enabled": true,
"windowSeconds": 3600,
"max": 10,
"keyBy": ["tenantId", "userId"]
}
}
}
}A route profile can inherit unspecified values from the global rate-limit configuration.
Memory store
The memory store is appropriate for:
- local development;
- tests;
- a single application process;
- monitor-only evaluation where exact distributed counts are not required.
It is not a distributed control. Each process maintains independent counters.
Custom stores
A rate-limit store implements:
interface RateLimitStore {
consume(key: string, limit: {
windowSeconds: number;
max: number;
}): Promise<{
allowed: boolean;
remaining: number;
resetAt: number;
}>;
}Pass the store through createDhal() or any framework adapter:
const protection = createDhal({
configPath: "dhal.json",
rateLimitStore: myStore
});For horizontally scaled production deployments, use the provided Redis-compatible store or another shared implementation with equivalent atomicity and expiry behaviour.