Documentation Index Fetch the complete documentation index at: https://unkey.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
Apply different rate limits to different users based on their subscription tier, role, or any other criteria. This recipe shows how to implement tiered rate limiting without hardcoding limits in your application.
The pattern
// Define limits per tier
const TIER_LIMITS = {
free : { limit : 100 , duration : "1h" },
pro : { limit : 1000 , duration : "1h" },
enterprise : { limit : 10000 , duration : "1h" },
};
// Get user's tier and apply appropriate limit
const tier = await getUserTier ( userId );
const config = TIER_LIMITS [ tier ];
const { success } = await limiter . limit ( userId , {
limit : config . limit ,
duration : config . duration ,
});
Full implementation
Next.js API Route
// app/api/route.ts
import { Ratelimit } from "@unkey/ratelimit" ;
import { headers } from "next/headers" ;
import { NextResponse } from "next/server" ;
// Initialize with default limits (will be overridden per-request)
const limiter = new Ratelimit ({
rootKey : process . env . UNKEY_ROOT_KEY ! ,
namespace : "api" ,
limit : 100 ,
duration : "1h" ,
});
const TIER_LIMITS : Record < string , { limit : number ; duration : string }> = {
free : { limit : 100 , duration : "1h" },
pro : { limit : 1000 , duration : "1h" },
enterprise : { limit : 10000 , duration : "1h" },
};
async function getUserTier ( userId : string ): Promise < string > {
// Replace with your actual user lookup
// e.g., database query, auth provider, etc.
const user = await db . users . findUnique ({ where : { id : userId } });
return user ?. tier ?? "free" ;
}
export async function POST ( request : Request ) {
const headersList = headers ();
const userId = headersList . get ( "x-user-id" );
if ( ! userId ) {
return NextResponse . json ({ error : "Missing user ID" }, { status : 401 });
}
// Get user's tier
const tier = await getUserTier ( userId );
const config = TIER_LIMITS [ tier ] ?? TIER_LIMITS . free ;
// Apply tier-specific rate limit by overriding the constructor defaults
const { success , remaining , reset } = await limiter . limit ( userId , {
limit : {
limit : config . limit ,
duration : config . duration as any ,
},
});
if ( ! success ) {
return NextResponse . json (
{
error : "Rate limit exceeded" ,
tier ,
reset : new Date ( reset ). toISOString (),
},
{
status : 429 ,
headers : {
"X-RateLimit-Limit" : config . limit . toString (),
"X-RateLimit-Remaining" : "0" ,
"X-RateLimit-Reset" : reset . toString (),
},
},
);
}
// Your API logic here
return NextResponse . json ({
message : "Success" ,
tier ,
remaining ,
});
}
Express Middleware
// middleware/ratelimit.ts
import { Ratelimit } from "@unkey/ratelimit" ;
import type { Request , Response , NextFunction } from "express" ;
const limiter = new Ratelimit ({
rootKey : process . env . UNKEY_ROOT_KEY ! ,
namespace : "api" ,
limit : 100 ,
duration : "1h" ,
});
const TIER_LIMITS : Record < string , { limit : number ; duration : string }> = {
free : { limit : 100 , duration : "1h" },
pro : { limit : 1000 , duration : "1h" },
enterprise : { limit : 10000 , duration : "1h" },
};
export function tieredRateLimit () {
return async ( req : Request , res : Response , next : NextFunction ) => {
const userId = req . headers [ "x-user-id" ] as string ;
if ( ! userId ) {
return res . status ( 401 ). json ({ error : "Missing user ID" });
}
// Get tier from your auth system
const tier = req . user ?. tier ?? "free" ;
const config = TIER_LIMITS [ tier ] ?? TIER_LIMITS . free ;
const { success , remaining , reset } = await limiter . limit ( userId , {
limit : {
limit : config . limit ,
duration : config . duration as any ,
},
});
// Always set rate limit headers
res . set ({
"X-RateLimit-Limit" : config . limit . toString (),
"X-RateLimit-Remaining" : remaining . toString (),
"X-RateLimit-Reset" : reset . toString (),
"X-RateLimit-Tier" : tier ,
});
if ( ! success ) {
return res . status ( 429 ). json ({
error : "Rate limit exceeded" ,
tier ,
retryAfter : Math . ceil (( reset - Date . now ()) / 1000 ),
});
}
next ();
};
}
Using Unkey overrides (recommended)
Instead of managing limits in your code, use Unkey overrides to set per-user limits dynamically. Overrides are managed via the separate Overrides class:
import { Overrides , Ratelimit } from "@unkey/ratelimit" ;
const limiter = new Ratelimit ({
rootKey : process . env . UNKEY_ROOT_KEY ! ,
namespace : "api" ,
limit : 100 , // Default for free tier
duration : "1h" ,
});
const overrides = new Overrides ({
rootKey : process . env . UNKEY_ROOT_KEY ! ,
});
// When a user upgrades to Pro, set an override
await overrides . setOverride ({
namespace : "api" ,
identifier : userId ,
limit : 1000 ,
duration : 3600000 , // 1h in ms
});
// Now this user automatically gets 1000/hour instead of 100
const { success } = await limiter . limit ( userId );
This approach means:
No code changes when limits change
Overrides can be managed via API or dashboard
Default limit applies to users without overrides
With API key verification
If you’re already using Unkey for API keys, attach rate limits directly to keys:
import { Unkey } from "@unkey/api" ;
const unkey = new Unkey ({ rootKey : process . env . UNKEY_ROOT_KEY ! });
// Create a Pro tier key with higher limits
try {
const { data } = await unkey . keys . createKey ({
apiId : "api_xxx" ,
name : "Pro User Key" ,
ratelimits : [
{
name : "requests" ,
limit : 1000 ,
duration : 3600000 , // 1 hour in ms
autoApply : true ,
},
],
meta : {
tier : "pro" ,
},
});
} catch ( err ) {
console . error ( err );
throw err ;
}
// Verification automatically enforces the key's rate limit
const { meta , data } = await unkey . keys . verifyKey ({ key : userKey });
if ( ! data . valid ) {
if ( data . code === "RATE_LIMITED" ) {
// Key-specific limit exceeded
}
}
Best practices
Use identifiers consistently Always use the same identifier format (user ID, org ID) for accurate
limiting across requests.
Communicate limits clearly Return rate limit headers so clients know their limits and can back off
gracefully.
Consider burst allowance Pro/Enterprise users often expect some burst capacity. Consider slightly
higher limits with shorter windows.
Log limit hits Track when users hit limits to inform pricing decisions and identify
potential abuse.
Next steps
Overrides Manage per-user limits without code changes
Tiered subscriptions Full subscription tier implementation