Response Headers
Communicate rate limit status to clients.
Standard Headers
http
X-RateLimit-Limit: 100 # Maximum requests in window
X-RateLimit-Remaining: 75 # Requests left
X-RateLimit-Reset: 1706123456 # Unix timestamp when limit resetsOn 429 (Too Many Requests):
http
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1706123456
Retry-After: 45 # Seconds to wait before retryEnable Headers
Headers are enabled by default:
typescript
new RateLimitPlugin({
includeHeaders: true, // Default
})Disable Headers
typescript
new RateLimitPlugin({
includeHeaders: false,
})INFO
includeHeaders is a module-level setting only. It cannot be overridden per-endpoint.
Custom Header Names
typescript
new RateLimitPlugin({
headers: {
limit: 'X-RateLimit-Limit',
remaining: 'X-RateLimit-Remaining',
reset: 'X-RateLimit-Reset',
retryAfter: 'Retry-After',
},
})Custom Names Example
typescript
new RateLimitPlugin({
headers: {
limit: 'X-API-Limit',
remaining: 'X-API-Remaining',
reset: 'X-API-Reset-Time',
retryAfter: 'X-Retry-After-Seconds',
},
})Header Values
X-RateLimit-Limit
Maximum requests allowed in the time window.
http
X-RateLimit-Limit: 100X-RateLimit-Remaining
Requests remaining before hitting the limit.
http
X-RateLimit-Remaining: 75 # 75 requests left
X-RateLimit-Remaining: 0 # Limit reachedX-RateLimit-Reset
Unix timestamp (seconds) when the rate limit resets.
http
X-RateLimit-Reset: 1706123456Convert to Date:
typescript
const resetTime = new Date(resetHeader * 1000);Retry-After
Seconds to wait before making another request (only on 429).
http
Retry-After: 45 # Wait 45 secondsClient-Side Handling
JavaScript/TypeScript
typescript
async function apiCall(): Promise<Response> {
const response = await fetch('/api/data');
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
const reset = parseInt(response.headers.get('X-RateLimit-Reset') || '0');
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
throw new RateLimitError(`Retry in ${retryAfter}s`);
}
if (remaining < 10) {
console.warn(`Approaching rate limit. ${remaining} requests left.`);
}
return response;
}Axios Interceptor
typescript
import axios from 'axios';
axios.interceptors.response.use(
(response) => {
const remaining = response.headers['x-ratelimit-remaining'];
if (remaining && parseInt(remaining) < 10) {
console.warn(`Rate limit warning: ${remaining} requests left`);
}
return response;
},
async (error) => {
if (error.response?.status === 429) {
const retryAfter = parseInt(error.response.headers['retry-after'] || '60');
await sleep(retryAfter * 1000);
return axios.request(error.config); // Retry
}
throw error;
}
);Fetch with Retry
typescript
async function fetchWithRetry(url: string, options?: RequestInit): Promise<Response> {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
console.log(`Rate limited. Retrying in ${retryAfter}s`);
await sleep(retryAfter * 1000);
return fetchWithRetry(url, options);
}
return response;
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}Proactive Rate Limiting
Track Remaining Requests
typescript
class RateLimitTracker {
private remaining: number = Infinity;
private reset: number = 0;
updateFromHeaders(headers: Headers): void {
const remaining = headers.get('X-RateLimit-Remaining');
const reset = headers.get('X-RateLimit-Reset');
if (remaining) this.remaining = parseInt(remaining);
if (reset) this.reset = parseInt(reset);
}
shouldWait(): boolean {
return this.remaining === 0 && Date.now() / 1000 < this.reset;
}
getWaitTime(): number {
return Math.max(0, this.reset - Date.now() / 1000);
}
}Pre-emptive Throttling
typescript
async function smartApiCall(tracker: RateLimitTracker): Promise<Response> {
// Wait if limit reached
if (tracker.shouldWait()) {
const waitTime = tracker.getWaitTime();
console.log(`Waiting ${waitTime}s for rate limit reset`);
await sleep(waitTime * 1000);
}
const response = await fetch('/api/data');
tracker.updateFromHeaders(response.headers);
return response;
}Display Rate Limit Info
React Component
tsx
import React, { useState, useEffect } from 'react';
function RateLimitIndicator() {
const [remaining, setRemaining] = useState<number | null>(null);
const [limit, setLimit] = useState<number | null>(null);
useEffect(() => {
// Update on each API call
const updateRateLimit = (headers: Headers) => {
setRemaining(parseInt(headers.get('X-RateLimit-Remaining') || '0'));
setLimit(parseInt(headers.get('X-RateLimit-Limit') || '0'));
};
// ... listen to API responses
}, []);
if (!remaining || !limit) return null;
const percentage = (remaining / limit) * 100;
const color = percentage > 50 ? 'green' : percentage > 20 ? 'orange' : 'red';
return (
<div>
<progress value={remaining} max={limit} />
<span style={{ color }}>
{remaining}/{limit} requests remaining
</span>
</div>
);
}Debugging Headers
View in Browser DevTools
- Open Network tab
- Make API request
- Check Response Headers section
- Look for X-RateLimit-* headers
curl Example
bash
curl -i https://api.example.com/data
# Output:
# HTTP/1.1 200 OK
# X-RateLimit-Limit: 100
# X-RateLimit-Remaining: 75
# X-RateLimit-Reset: 1706123456Troubleshooting
Headers Not Appearing
Check configuration:
typescript
new RateLimitPlugin({
includeHeaders: true, // Must be true
})Verify module configuration:
typescript
new RateLimitPlugin({
includeHeaders: true, // Must be true (default)
})CORS Issues
Allow headers in CORS config:
typescript
app.enableCors({
exposedHeaders: [
'X-RateLimit-Limit',
'X-RateLimit-Remaining',
'X-RateLimit-Reset',
'Retry-After',
],
});Next Steps
- Monitoring — Track rate limits
- Testing — Test with headers