Appearance
REST API - Rate Limiting
Complete guide to understanding and managing API rate limits to ensure optimal performance and fair usage.
📋 Table of Contents
- Overview
- Rate Limit Tiers
- Rate Limit Headers
- Handling Rate Limits
- Best Practices
- Implementation Examples
- Monitoring Usage
- Troubleshooting
Overview
Rate limiting protects the API from abuse and ensures fair usage for all clients. The HelpDesk Pro API implements rate limiting based on:
- Endpoint Type - Different limits for different endpoints
- User Authentication - Authenticated users have higher limits
- IP Address - Limits apply per IP for unauthenticated requests
Why Rate Limiting?
- ✅ Prevents Abuse - Protects against DDoS and spam
- ✅ Ensures Fair Usage - Distributes resources fairly
- ✅ Maintains Performance - Keeps API responsive
- ✅ Cost Control - Manages server resources
Rate Limit Tiers
General API Endpoints
Limit: 60 requests per minute
Applies to:
- All authenticated endpoints
- Per user (if authenticated) or per IP (if unauthenticated)
Examples:
GET /api/v1/ticketsPOST /api/v1/ticketsGET /api/v1/contactsGET /api/v1/dashboard
Authentication Endpoints
Limit: 5 requests per minute
Applies to:
- Per IP address (to prevent brute force attacks)
Examples:
POST /api/v1/auth/loginPOST /api/v1/auth/registerPOST /api/v1/auth/password/reset
Rate Limit Window
- Window: 1 minute (60 seconds)
- Reset: Automatically resets every minute
- Tracking: Per user ID or IP address
Rate Limit Headers
Every API response includes rate limit information in headers:
Response Headers
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
Retry-After: 15| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit | Maximum requests allowed per window | 60 |
X-RateLimit-Remaining | Requests remaining in current window | 45 |
Retry-After | Seconds to wait before retrying (only on 429) | 15 |
Example Response
http
HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
{
"success": true,
"data": [...]
}Rate Limit Exceeded Response
http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Retry-After: 15
{
"success": false,
"message": "Too Many Requests",
"meta": {
"timestamp": "2025-01-15T10:30:00.000000Z",
"version": "v1"
}
}Handling Rate Limits
Detecting Rate Limits
Check for 429 status code in responses:
javascript
const response = await fetch(url, options);
if (response.status === 429) {
// Rate limit exceeded
const retryAfter = response.headers.get('Retry-After');
console.log(`Rate limited. Retry after ${retryAfter} seconds`);
}Implementing Retry Logic
Basic Retry:
javascript
async function requestWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
console.log(`Rate limited. Waiting ${retryAfter} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}Exponential Backoff:
javascript
async function requestWithExponentialBackoff(url, options, maxRetries = 5) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
const waitTime = Math.min(retryAfter * 1000, Math.pow(2, i) * 1000);
console.log(`Rate limited. Waiting ${waitTime}ms...`);
await new Promise(resolve => setTimeout(resolve, waitTime));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}Monitoring Rate Limit Headers
Track your usage to avoid hitting limits:
javascript
class RateLimitTracker {
constructor() {
this.limit = 60;
this.remaining = 60;
}
updateFromHeaders(headers) {
this.limit = parseInt(headers.get('X-RateLimit-Limit') || '60');
this.remaining = parseInt(headers.get('X-RateLimit-Remaining') || '0');
}
getUsagePercentage() {
return ((this.limit - this.remaining) / this.limit) * 100;
}
isNearLimit(threshold = 80) {
return this.getUsagePercentage() >= threshold;
}
getRemainingRequests() {
return this.remaining;
}
}
// Usage
const tracker = new RateLimitTracker();
const response = await fetch(url, options);
tracker.updateFromHeaders(response.headers);
if (tracker.isNearLimit()) {
console.warn('Approaching rate limit. Consider throttling requests.');
}
console.log(`Remaining requests: ${tracker.getRemainingRequests()}`);Best Practices
1. Implement Request Throttling
Limit your request rate to stay under limits:
javascript
class RequestThrottler {
constructor(maxRequests, windowMs) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = [];
}
async wait() {
const now = Date.now();
// Remove old requests outside the window
this.requests = this.requests.filter(
time => now - time < this.windowMs
);
// If at limit, wait until oldest request expires
if (this.requests.length >= this.maxRequests) {
const oldestRequest = this.requests[0];
const waitTime = this.windowMs - (now - oldestRequest);
await new Promise(resolve => setTimeout(resolve, waitTime));
this.requests.shift();
}
this.requests.push(Date.now());
}
}
// Usage: Limit to 50 requests per minute (under the 60 limit)
const throttler = new RequestThrottler(50, 60000);
async function makeRequest() {
await throttler.wait();
return fetch(url, options);
}2. Cache Responses
Reduce API calls by caching responses:
javascript
class APICache {
constructor(ttl = 60000) { // 1 minute default
this.cache = new Map();
this.ttl = ttl;
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() - item.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}
return item.data;
}
set(key, data) {
this.cache.set(key, {
data,
timestamp: Date.now(),
});
}
}
const cache = new APICache(60000); // 1 minute cache
async function getCachedTickets() {
const cacheKey = 'tickets';
const cached = cache.get(cacheKey);
if (cached) {
return cached;
}
const response = await fetch('/api/v1/tickets');
const data = await response.json();
cache.set(cacheKey, data);
return data;
}3. Batch Requests
Combine multiple requests when possible:
javascript
// Instead of multiple requests:
// GET /api/v1/tickets/1
// GET /api/v1/tickets/2
// GET /api/v1/tickets/3
// Use filtering:
// GET /api/v1/tickets?id=1,2,34. Use Webhooks
For real-time updates, use webhooks instead of polling:
javascript
// Instead of polling every second:
setInterval(() => {
fetch('/api/v1/tickets');
}, 1000);
// Use webhooks for updates
app.post('/webhook/tickets', (req, res) => {
// Handle ticket updates
});5. Monitor Usage
Track your API usage to optimize:
javascript
class UsageMonitor {
constructor() {
this.requests = [];
this.windowMs = 60000; // 1 minute
}
recordRequest() {
const now = Date.now();
this.requests = this.requests.filter(
time => now - time < this.windowMs
);
this.requests.push(now);
}
getRequestRate() {
return this.requests.length;
}
getAverageRequestRate() {
if (this.requests.length === 0) return 0;
const oldest = this.requests[0];
const newest = this.requests[this.requests.length - 1];
const duration = (newest - oldest) / 1000; // seconds
return duration > 0 ? this.requests.length / duration * 60 : 0;
}
}Implementation Examples
JavaScript/TypeScript
typescript
class RateLimitedAPI {
private baseURL: string;
private token: string;
private throttler: RequestThrottler;
private tracker: RateLimitTracker;
constructor(baseURL: string, token: string) {
this.baseURL = baseURL;
this.token = token;
this.throttler = new RequestThrottler(50, 60000); // 50 req/min
this.tracker = new RateLimitTracker();
}
async request(endpoint: string, options: RequestInit = {}) {
// Wait for throttler
await this.throttler.wait();
const response = await fetch(`${this.baseURL}${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
...options.headers,
},
});
// Update tracker
this.tracker.updateFromHeaders(response.headers);
// Handle rate limit
if (response.status === 429) {
const retryAfter = parseInt(
response.headers.get('Retry-After') || '60'
);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
return this.request(endpoint, options); // Retry
}
return response;
}
getUsageInfo() {
return {
limit: this.tracker.limit,
remaining: this.tracker.remaining,
usagePercentage: this.tracker.getUsagePercentage(),
};
}
}Python
python
import time
import requests
from collections import deque
from typing import Optional
class RateLimitedAPI:
def __init__(self, base_url: str, token: str, max_requests: int = 50):
self.base_url = base_url
self.token = token
self.max_requests = max_requests
self.window_seconds = 60
self.requests = deque()
self.limit = 60
self.remaining = 60
def _wait_if_needed(self):
now = time.time()
# Remove old requests
while self.requests and now - self.requests[0] > self.window_seconds:
self.requests.popleft()
# Wait if at limit
if len(self.requests) >= self.max_requests:
oldest = self.requests[0]
wait_time = self.window_seconds - (now - oldest)
if wait_time > 0:
time.sleep(wait_time)
self.requests.popleft()
self.requests.append(time.time())
def request(self, method: str, endpoint: str, **kwargs):
self._wait_if_needed()
url = f"{self.base_url}{endpoint}"
headers = {
'Authorization': f'Bearer {self.token}',
'Content-Type': 'application/json',
'Accept': 'application/json',
**kwargs.get('headers', {}),
}
response = requests.request(method, url, headers=headers, **kwargs)
# Update rate limit info
self.limit = int(response.headers.get('X-RateLimit-Limit', 60))
self.remaining = int(response.headers.get('X-RateLimit-Remaining', 0))
# Handle rate limit
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
time.sleep(retry_after)
return self.request(method, endpoint, **kwargs) # Retry
return response
def get_usage_info(self):
return {
'limit': self.limit,
'remaining': self.remaining,
'usage_percentage': ((self.limit - self.remaining) / self.limit) * 100,
}Monitoring Usage
Track Rate Limit Headers
javascript
function trackRateLimitUsage(response) {
const limit = parseInt(response.headers.get('X-RateLimit-Limit') || '60');
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
const usage = ((limit - remaining) / limit) * 100;
console.log(`API Usage: ${usage.toFixed(1)}% (${remaining}/${limit} remaining)`);
if (usage > 80) {
console.warn('⚠️ Approaching rate limit!');
}
// Send to analytics
analytics.track('api_rate_limit_usage', {
limit,
remaining,
usage,
});
}Alert on High Usage
javascript
class RateLimitAlert {
constructor(threshold = 80) {
this.threshold = threshold;
this.alerted = false;
}
check(usagePercentage) {
if (usagePercentage >= this.threshold && !this.alerted) {
this.alerted = true;
this.sendAlert(usagePercentage);
} else if (usagePercentage < this.threshold) {
this.alerted = false;
}
}
sendAlert(usagePercentage) {
console.warn(`🚨 Rate limit usage at ${usagePercentage}%!`);
// Send email, Slack notification, etc.
}
}Troubleshooting
Issue: Frequently Hitting Rate Limits
Solutions:
- Implement request throttling
- Cache responses when possible
- Reduce polling frequency
- Use webhooks instead of polling
- Batch multiple requests
Issue: 429 Errors Even with Throttling
Solutions:
- Check if multiple clients are using the same token
- Verify throttling is working correctly
- Reduce max requests per minute
- Implement exponential backoff
Issue: Inconsistent Rate Limit Headers
Solutions:
- Ensure you're reading headers correctly
- Check for proxy/CDN modifying headers
- Verify you're using the latest API version
- Contact support if issue persists
Next Steps
- Error Handling - Handle 429 errors properly
- Code Examples - See rate limiting in action
- Getting Started - API basics
For questions about rate limits or to request higher limits, contact our API support team.