Rate Limiting
Rate limiting rules and thresholds across ZoneVast services. Learn about global limits, OTP tiered limits, login lockout, and how to handle 429 responses.
Rate Limiting
ZoneVast services enforce rate limits to protect the platform. Limits vary by endpoint type and are implemented at multiple layers.
Global Rate Limit
All NestJS services (zv-flex-auth-service) enforce a global rate limit:
|| Setting | Value | ||---------|-------| || Limit | 10 requests per minute | || Scope | Per IP address | || Applied to | All endpoints |
Django services (zv-auth-service, zv-project-service) currently do not enforce global rate limiting at the application level. Rate limiting may be applied at the API Gateway level.
OTP Tiered Limits
OTP-sending endpoints use a tiered cooldown system. Each successive OTP request increases the wait time:
|| Attempt | Cooldown | ||---------|----------| || 1st | Immediate (no wait) | || 2nd | 1 minute | || 3rd | 5 minutes | || 4th and beyond | 24 hours |
Affected Endpoints
POST /auth/api/v2/auth/send-otp(Flex Auth Service)POST /auth/api/v2/auth/register-init(Flex Auth Service)POST /auth/api/v2/auth/password/reset/init(Flex Auth Service)
Response When Cooldown Active
{
"statusCode": 429,
"message": "Please wait 300 seconds before requesting a new OTP",
"timestamp": "2026-04-27T12:00:00.000Z"
}
Login Lockout
After 5 consecutive failed login attempts, the account is locked for 15 minutes.
|| Setting | Value | ||---------|-------| || Failed attempts before lockout | 5 | || Lockout duration | 15 minutes | || Reset on success | Yes (counter resets) |
Affected Endpoints
POST /auth/api/v2/auth/login(Flex Auth Service — phone + password)POST /api/v1/auth/auth/token/(Auth Service — username + password)
Per-Endpoint Limits
NestJS services apply stricter limits on sensitive endpoints:
|| Endpoint | Limit | Window |
||----------|-------|--------|
|| register-init | 3 requests | 5 minutes |
|| login | 5 requests | 5 minutes |
|| send-otp | 3 requests | 5 minutes |
|| login-otp | 5 requests | 5 minutes |
|| password/reset/init | 3 requests | 5 minutes |
Response Headers
Rate-limited responses may include these headers:
|| Header | Description |
||--------|-------------|
|| Retry-After | Seconds until you can retry |
|| X-RateLimit-Limit | Maximum requests in the current window |
|| X-RateLimit-Remaining | Requests remaining in current window |
Handling Rate Limits in Your Client
Detect Rate Limiting
async function apiCall(url: string, options: RequestInit) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
const data = await response.json();
throw new RateLimitError(
data.message || 'Rate limit exceeded',
retryAfter ? parseInt(retryAfter) : 60
);
}
return response;
}
Retry with Backoff
async function callWithRetry(
url: string,
options: RequestInit,
maxRetries = 3
): Promise<Response> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
const delay = retryAfter
? parseInt(retryAfter) * 1000
: Math.pow(2, attempt) * 1000;
if (attempt < maxRetries) {
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
}
return response;
}
throw new Error('Max retries exceeded');
}
Best Practices
- Cache tokens — Don't re-authenticate on every request. Access tokens last 60 minutes.
- Implement retry logic — Use exponential backoff with
Retry-Afterheader. - Show cooldown timers — For OTP endpoints, show the user how long to wait.
- Queue requests — Batch operations instead of rapid sequential calls.
- Monitor remaining limits — Track
X-RateLimit-Remainingto avoid hitting limits.
What's Next
- Error Responses — How error responses are structured
- Headers & Authentication — Required headers
- Service Integration — How services communicate