This guide covers rate limiting functionality including namespace creation, override management, and rate limit checking.
Overview
Rate limiting endpoints manage request limits, overrides, and namespace-based rate limiting across your API infrastructure.
Key Changes in v2:
- Response format:
result → {meta, data} wrapper
- Rate limit structure: Single
ratelimit object → ratelimits array with named limits
- Override management: Enhanced override response format with additional metadata
- Async handling: Removed
async parameter
- Auto apply: Added
autoApply parameter to apply ratelimits to key verifications automatically
- Resources: Removed
resources array
- Metadata: Removed
meta object from request body
Migration Impact:
- Existing in v1: Full rate limiting and override management functionality
- Enhanced in v2: Improved response format, better override metadata, and new listing capabilities
- Maintained in v2: All core rate limiting functionality with backward-compatible request formats
Rate Limit Checking
POST /v1/ratelimits.limit → POST /v2/ratelimits.limit
Key Changes:
- Response format: Direct response →
{meta, data} envelope
- Enhanced response with additional metadata
- Better override handling
{
"namespace": "email_sending",
"identifier": "user_123",
"limit": 100,
"duration": 3600000,
"cost": 1,
"async": false,
"meta": {},
"resources": [
{
"type": "project",
"id": "p_123",
"name": "unkey"
}
]
}
// v1 Response (direct response)
{
"success": true,
"limit": 100,
"remaining": 99,
"reset": 1672531200000
}
// v2 Response
{
"meta": {
"requestId": "req_ratelimit123"
},
"data": {
"success": true,
"limit": 100,
"remaining": 99,
"reset": 1672531200000,
"overrideId": "rlor_123"
}
}
curl -X POST https://api.unkey.dev/v1/ratelimits.limit \
-H "Authorization: Bearer <your-root-key>" \
-H "Content-Type: application/json" \
-d '{
"namespace": "email_sending",
"identifier": "user_123",
"limit": 100,
"duration": 3600000,
"cost": 1
}'
curl -X POST https://api.unkey.com/v2/ratelimits.limit \
-H "Authorization: Bearer <your-root-key>" \
-H "Content-Type: application/json" \
-d '{
"namespace": "email_sending",
"identifier": "user_123",
"limit": 100,
"duration": 3600000,
"cost": 1
}'
Rate Limit Overrides
POST /v1/ratelimits.setOverride → POST /v2/ratelimits.setOverride
Key Changes:
- Response format: Direct response →
{meta, data} envelope
- Enhanced override targeting options
- Better validation and error handling
Request Structure
Response Changes
cURL Examples
{
"namespace": "api_requests",
"identifier": "premium_user_456",
"limit": 10000,
"duration": 3600000,
"async": false
}
Set Override Response Diff
// v1 Response (direct response)
{
"overrideId": "rlor_123"
}
// v2 Response
{
"meta": {
"requestId": "req_setoverride456"
},
"data": {
"overrideId": "rlor_123"
}
}
curl -X POST https://api.unkey.dev/v1/ratelimits.setOverride \
-H "Authorization: Bearer <your-root-key>" \
-H "Content-Type: application/json" \
-d '{
"namespace": "api_requests",
"identifier": "premium_user_456",
"limit": 10000,
"duration": 3600000
}'
curl -X POST https://api.unkey.com/v2/ratelimits.setOverride \
-H "Authorization: Bearer <your-root-key>" \
-H "Content-Type: application/json" \
-d '{
"namespace": "api_requests",
"identifier": "premium_user_456",
"limit": 10000,
"duration": 3600000
}'
GET /v1/ratelimits.getOverride → POST /v2/ratelimits.getOverride
Key Changes:
- HTTP method: GET → POST
- Request format: Query parameters → Request body
- Response format: Direct response →
{meta, data} envelope
Request & Response
cURL Examples
{
"namespace": "api_requests",
"identifier": "premium_user_456"
}
Get Override Response Diff
// v1 Response (direct response)
{
"id": "over_123",
"identifier": "premium_user_456",
"limit": 10000,
"duration": 3600000,
"async": false
}
// v2 Response
{
"meta": {
"requestId": "req_getoverride789"
},
"data": {
"overrideId": "override_abc123",
"identifier": "premium_user_456",
"limit": 10000,
"duration": 3600000
}
}
curl -X GET "https://api.unkey.dev/v1/ratelimits.getOverride?identifier=premium_user_456&namespaceName=api_requests" \
-H "Authorization: Bearer <your-root-key>"
curl -X POST https://api.unkey.com/v2/ratelimits.getOverride \
-H "Authorization: Bearer <your-root-key>" \
-H "Content-Type: application/json" \
-d '{
"namespace": "api_requests",
"identifier": "premium_user_456"
}'
GET /v1/ratelimits.listOverrides → POST /v2/ratelimits.listOverrides
Purpose: Get paginated list of all overrides in a namespace.
Key Changes:
- HTTP method: GET → POST
- Request format: Query parameters → Request body
- Response format: Direct response →
{meta, data} envelope
Request Structure
Response Example
cURL Examples
{
"namespace": "api_requests",
"limit": 100,
"cursor": "optional_cursor"
}
List Overrides Response Diff
// v1 Response (direct response)
{
"overrides": [
{
"id": "override_abc123",
"identifier": "premium_user_456",
"limit": 10000,
"duration": 3600000,
"async": false
}
],
"cursor": "next_page_cursor_here",
"total": 42
}
// v2 Response (envelope format)
{
"meta": {
"requestId": "req_listoverrides123"
},
"data": {
"overrides": [
{
"overrideId": "override_abc123",
"identifier": "premium_user_456",
"limit": 10000,
"duration": 3600000,
}
],
"cursor": "next_page_cursor_here"
}
}
curl -X GET "https://api.unkey.dev/v1/ratelimits.listOverrides?namespaceName=api_requests&limit=100" \
-H "Authorization: Bearer <your-root-key>"
curl -X POST https://api.unkey.com/v2/ratelimits.listOverrides \
-H "Authorization: Bearer <your-root-key>" \
-H "Content-Type: application/json" \
-d '{
"namespace": "api_requests",
"limit": 100,
"cursor": "optional_cursor"
}'
POST /v1/ratelimits.deleteOverride → POST /v2/ratelimits.deleteOverride
Key Changes:
- Response format: Direct response →
{meta, data} envelope
Request & Response
cURL Examples
{
"namespace": "api_requests",
"identifier": "premium_user_456"
}
Delete Override Response Diff
// v1 Response (direct empty response)
{}
// v2 Response
{
"meta": {
"requestId": "req_deleteoverride999"
},
"data": {}
}
curl -X POST https://api.unkey.dev/v1/ratelimits.deleteOverride \
-H "Authorization: Bearer <your-root-key>" \
-H "Content-Type: application/json" \
-d '{
"namespace": "api_requests",
"identifier": "premium_user_456"
}'
curl -X POST https://api.unkey.com/v2/ratelimits.deleteOverride \
-H "Authorization: Bearer <your-root-key>" \
-H "Content-Type: application/json" \
-d '{
"namespace": "api_requests",
"identifier": "premium_user_456"
}'
Key-Level Rate Limiting Changes
v1 Single Rate Limit → v2 Multiple Named Rate Limits
Key Creation Changes
Key Verification Changes
Response Changes
Key Rate Limit Structure Migration
// v1 Key Creation
{
"apiId": "api_123",
"ratelimit": {
"limit": 1000,
"duration": 3600000,
"async": true
}
}
// v2 Key Creation
{
"apiId": "api_123",
"ratelimits": [
{
"name": "api_requests",
"limit": 1000,
"duration": 3600000,
"autoApply": true
},
{
"name": "heavy_operations",
"limit": 10,
"duration": 60000,
"autoApply": false
}
]
}
Rate Limit Verification Migration
// v1 Key Verification Request
{
"key": "sk_123"
}
// v2 Key Verification Request with Named Rate Limits
{
"key": "sk_123",
"ratelimits": [
{
"name": "api_requests",
"cost": 1
},
{
"name": "heavy_operations",
"cost": 5
}
]
}
Rate Limit Response Migration
// v1 Response
{
"result": {
"valid": true,
"ratelimit": {
"limit": 1000,
"remaining": 999,
"reset": 1672531200000
}
}
}
// v2 Response
{
"data": {
"valid": true,
"ratelimits": [
{
"id": "rl_123",
"name": "api_requests",
"limit": 1000,
"remaining": 999,
"reset": 1672531200000,
"exceeded": false,
"duration": 3600000,
"autoApply": true
},
{
"id": "rl_456",
"name": "heavy_operations",
"limit": 10,
"remaining": 5,
"reset": 1672531200000,
"exceeded": false,
"duration": 60000,
"autoApply": false
}
]
}
}
Migration Patterns
v1 vs v2: Response Handling
// v1: Direct response access
const rateLimit = await fetch('/v1/ratelimits.limit', {
method: 'POST',
headers: {
'Authorization': 'Bearer <root-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
namespace: 'api_calls',
identifier: 'user_123',
limit: 100,
duration: 3600000
})
});
const data = await rateLimit.json();
const success = data.success; // v1 direct format
const remaining = data.remaining;
// v2: Access data through data field
const rateLimit = await fetch('/v2/ratelimits.limit', {
method: 'POST',
headers: {
'Authorization': 'Bearer <root-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
namespace: 'api_calls',
identifier: 'user_123',
limit: 100,
duration: 3600000
})
});
const response = await rateLimit.json();
const success = response.data.success; // v2 format
const remaining = response.data.remaining;
const requestId = response.meta.requestId; // for debugging
Key-Level Rate Limiting Migration
v1 vs v2: Key Rate Limit Structure
// v1: Single Rate Limit
{
"apiId": "api_123",
"ratelimit": {
"limit": 1000,
"duration": 3600000,
"async": true
}
}
// v2: Multiple Named Rate Limits
{
"apiId": "api_123",
"ratelimits": [
{
"name": "general_requests",
"limit": 1000,
"duration": 3600000,
"autoApply": true
},
{
"name": "expensive_ops",
"limit": 10,
"duration": 60000,
"autoApply": false
}
]
}
Override Management Patterns
// Set override (same in v1 & v2)
const override = await fetch('/v2/ratelimits.setOverride', {
method: 'POST',
headers: {
'Authorization': 'Bearer <root-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
namespace: 'api_requests',
identifier: 'premium_user',
limit: 10000,
duration: 3600000
})
});
// Get override (same in v1 & v2)
const existing = await fetch('/v2/ratelimits.getOverride', {
method: 'POST',
headers: {
'Authorization': 'Bearer <root-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
namespace: 'api_requests',
identifier: 'premium_user'
})
});
const result = await existing.json();
const limit = result.data.limit; // v2: access via data
v2: List and Batch Management
// v2: List all overrides (new capability)
const overrides = await fetch('/v2/ratelimits.listOverrides', {
method: 'POST',
headers: {
'Authorization': 'Bearer <root-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
namespace: 'api_requests',
limit: 100
})
});
const result = await overrides.json();
const overrideList = result.data.overrides;
// Process overrides in batches
for (const override of overrideList) {
if (override.limit < 1000) {
// Update low-limit overrides
await fetch('/v2/ratelimits.setOverride', {
method: 'POST',
headers: {
'Authorization': 'Bearer <root-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
namespace: override.namespace,
identifier: override.identifier,
limit: 1000,
duration: override.duration
})
});
}
}
Advanced Features in v2
Multiple Rate Limits per Key
Complex rate limiting setup
{
"apiId": "api_123",
"ratelimits": [
{
"name": "requests_per_minute",
"limit": 60,
"duration": 60000,
"autoApply": true
},
{
"name": "requests_per_hour",
"limit": 1000,
"duration": 3600000,
"autoApply": true
},
{
"name": "expensive_operations",
"limit": 5,
"duration": 300000,
"autoApply": false
}
]
}
Named Rate Limit Targeting
Selective rate limit application
{
"key": "sk_123",
"ratelimits": [
{
"name": "expensive_operations",
"cost": 1
}
]
}
Only applies cost to the “expensive_operations” rate limit, leaving others unchanged.
Batch Override Management
Managing multiple overrides
// List all overrides in namespace
const overrides = await unkey.ratelimits.listOverrides({
namespace: "api_requests"
});
// Process overrides in batches
for (const override of overrides.data?.overrides || []) {
if (override.limit < 1000) {
// Update low-limit overrides
await unkey.ratelimits.setOverride({
namespace: override.namespace,
identifier: override.identifier,
limit: 1000,
duration: override.duration
});
}
}
Migration Checklist
Key-Level Rate Limiting Updates
Override Management Updates
Enhanced Features
Advanced Rate Limiting Patterns
Testing
Last modified on February 14, 2026