Rate Limiting
Rate limiting protects the Billoget API from abuse and ensures fair usage across all users. This guide explains how rate limiting works and how to handle it in your applications.
π¦ How Rate Limiting Worksβ
Sliding Window Algorithmβ
The Billoget API uses a sliding window algorithm to track request rates. This means:
- Requests are counted per hour from the first request
- The window slides continuously, not in fixed hourly blocks
- More accurate and fair than fixed time windows
Per-API Key Limitsβ
Each API key has its own rate limit, configurable when creating the key:
{
"rateLimitPerHour": 1000
}
π Default Limitsβ
Standard Limitsβ
API Key Type | Default Limit | Configurable |
---|---|---|
Standard | 1,000 req/hour | β Yes (up to 10,000) |
Premium | 5,000 req/hour | β Yes (up to 50,000) |
Enterprise | 25,000 req/hour | β Yes (unlimited) |
Endpoint-Specific Limitsβ
Some endpoints may have additional limits:
Endpoint | Additional Limit |
---|---|
POST /api/public/webhooks/test | 10 req/minute |
File uploads | 100 MB/hour |
π Rate Limit Headersβ
Every API response includes rate limit information in the headers:
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1642694400
X-RateLimit-Window: 3600
Header Descriptionsβ
Header | Description |
---|---|
X-RateLimit-Limit | Maximum requests allowed per hour |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
X-RateLimit-Window | Window size in seconds (3600 = 1 hour) |
β οΈ Rate Limit Exceededβ
When you exceed your rate limit, you'll receive a 429 Too Many Requests
response:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1642697000
Retry-After: 3600
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded. Try again in 3600 seconds",
"details": {
"limit": 1000,
"remaining": 0,
"resetAt": "2024-01-15T11:30:00Z"
}
}
Retry-After Headerβ
The Retry-After
header tells you how many seconds to wait before making another request.
π οΈ Handling Rate Limitsβ
1. Check Headers Before Requestsβ
class BillogetAPI {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = "https://api.billoget.com/v1";
this.rateLimitInfo = {
limit: null,
remaining: null,
reset: null,
};
}
async makeRequest(endpoint, options = {}) {
// Check if we're close to the limit
if (this.rateLimitInfo.remaining < 10) {
console.warn("Approaching rate limit. Consider slowing down requests.");
}
const response = await fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers: {
Authorization: `Bearer ${this.apiKey}`,
"Content-Type": "application/json",
...options.headers,
},
});
// Update rate limit info from headers
this.updateRateLimitInfo(response.headers);
return response;
}
updateRateLimitInfo(headers) {
this.rateLimitInfo = {
limit: parseInt(headers.get("X-RateLimit-Limit")),
remaining: parseInt(headers.get("X-RateLimit-Remaining")),
reset: parseInt(headers.get("X-RateLimit-Reset")),
};
}
}
2. Implement Exponential Backoffβ
async function makeRequestWithBackoff(apiCall, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await apiCall();
if (response.status === 429) {
const retryAfter = response.headers.get("Retry-After");
const delay = retryAfter
? parseInt(retryAfter) * 1000
: Math.pow(2, attempt) * 1000;
console.log(
`Rate limited. Waiting ${delay}ms before retry ${
attempt + 1
}/${maxRetries}`
);
await sleep(delay);
continue;
}
return response;
} catch (error) {
if (attempt === maxRetries - 1) throw error;
await sleep(Math.pow(2, attempt) * 1000);
}
}
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
3. Request Queuingβ
class RateLimitedQueue {
constructor(apiKey, requestsPerHour = 1000) {
this.apiKey = apiKey;
this.requestsPerHour = requestsPerHour;
this.queue = [];
this.processing = false;
this.requestTimes = [];
}
async addRequest(requestFn) {
return new Promise((resolve, reject) => {
this.queue.push({ requestFn, resolve, reject });
this.processQueue();
});
}
async processQueue() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
while (this.queue.length > 0) {
// Clean old request times (older than 1 hour)
const oneHourAgo = Date.now() - 60 * 60 * 1000;
this.requestTimes = this.requestTimes.filter((time) => time > oneHourAgo);
// Check if we can make a request
if (this.requestTimes.length >= this.requestsPerHour) {
const oldestRequest = Math.min(...this.requestTimes);
const waitTime = oldestRequest + 60 * 60 * 1000 - Date.now();
if (waitTime > 0) {
await sleep(waitTime);
continue;
}
}
// Process next request
const { requestFn, resolve, reject } = this.queue.shift();
try {
this.requestTimes.push(Date.now());
const result = await requestFn();
resolve(result);
} catch (error) {
reject(error);
}
// Small delay between requests
await sleep(100);
}
this.processing = false;
}
}
π Monitoring Rate Limitsβ
Real-time Monitoringβ
class RateLimitMonitor {
constructor(apiKey) {
this.apiKey = apiKey;
this.stats = {
requestsThisHour: 0,
limit: 1000,
remaining: 1000,
resetTime: null,
};
}
updateFromHeaders(headers) {
this.stats.limit = parseInt(headers.get("X-RateLimit-Limit"));
this.stats.remaining = parseInt(headers.get("X-RateLimit-Remaining"));
this.stats.resetTime = new Date(
parseInt(headers.get("X-RateLimit-Reset")) * 1000
);
this.stats.requestsThisHour = this.stats.limit - this.stats.remaining;
}
getUsagePercentage() {
return ((this.stats.requestsThisHour / this.stats.limit) * 100).toFixed(2);
}
getTimeUntilReset() {
if (!this.stats.resetTime) return null;
return Math.max(0, this.stats.resetTime.getTime() - Date.now());
}
shouldSlowDown() {
return this.getUsagePercentage() > 80;
}
logStatus() {
console.log(`Rate Limit Status:
Usage: ${this.getUsagePercentage()}% (${this.stats.requestsThisHour}/${
this.stats.limit
})
Remaining: ${this.stats.remaining}
Reset in: ${Math.round(this.getTimeUntilReset() / 1000 / 60)} minutes
`);
}
}
Usage Analyticsβ
Get detailed rate limit analytics from the API:
curl -X GET "https://api.billoget.com/api-keys/api_key_123/stats?period=24h" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Response includes rate limit information:
{
"rateLimitStats": {
"currentUsage": 750,
"limit": 1000,
"percentage": 75.0,
"peakUsage": 950,
"peakTime": "2024-01-15T14:30:00Z",
"rateLimitHits": 3,
"averageRequestsPerHour": 680
}
}
π― Best Practicesβ
1. Respect Rate Limitsβ
Always check rate limit headers and adjust your request rate accordingly.
2. Implement Cachingβ
Cache responses when possible to reduce API calls:
class CachedAPI {
constructor(apiKey, cacheTTL = 300000) {
// 5 minutes
this.apiKey = apiKey;
this.cache = new Map();
this.cacheTTL = cacheTTL;
}
async get(endpoint) {
const cacheKey = `GET:${endpoint}`;
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
return cached.data;
}
const response = await this.makeRequest(endpoint);
const data = await response.json();
this.cache.set(cacheKey, {
data,
timestamp: Date.now(),
});
return data;
}
}
3. Batch Operationsβ
Group multiple operations into single requests when possible.
4. Use Webhooksβ
Instead of polling for changes, use webhooks to get real-time notifications.
5. Monitor Usageβ
Regularly check your usage patterns and adjust limits if needed.
π¨ Common Scenariosβ
High-Volume Applicationsβ
For applications that need higher limits:
# Request a higher rate limit
curl -X PUT "https://api.billoget.com/api-keys/api_key_123" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"rateLimitPerHour": 5000
}'
Burst Trafficβ
Handle sudden spikes in traffic:
class BurstHandler {
constructor(apiKey, burstLimit = 100) {
this.apiKey = apiKey;
this.burstLimit = burstLimit;
this.burstCount = 0;
this.burstResetTime = Date.now() + 60000; // 1 minute
}
async handleBurst(requests) {
const now = Date.now();
// Reset burst counter every minute
if (now > this.burstResetTime) {
this.burstCount = 0;
this.burstResetTime = now + 60000;
}
// Check if burst limit would be exceeded
if (this.burstCount + requests.length > this.burstLimit) {
throw new Error("Burst limit exceeded. Please slow down requests.");
}
this.burstCount += requests.length;
return Promise.all(requests.map((req) => this.makeRequest(req)));
}
}
π§ Testing Rate Limitsβ
Test Scriptβ
async function testRateLimit(apiKey) {
const api = new BillogetAPI(apiKey);
let requestCount = 0;
console.log("Testing rate limit...");
try {
while (true) {
const response = await api.makeRequest("/api/public/budgets?limit=1");
requestCount++;
const remaining = response.headers.get("X-RateLimit-Remaining");
console.log(`Request ${requestCount}, Remaining: ${remaining}`);
if (response.status === 429) {
console.log("Rate limit reached!");
break;
}
// Small delay to avoid overwhelming the server
await sleep(10);
}
} catch (error) {
console.error("Error:", error.message);
}
}
π Next Stepsβ
Now that you understand rate limiting, let's learn about:
- Error Handling - Handle API errors gracefully
- API Reference - Explore available endpoints
- API Reference - Explore all available endpoints
π Additional Resourcesβ
- API Reference - Complete endpoint documentation
- Postman Collection - Test rate limiting with examples
- Error Handling - Handle rate limit errors gracefully
Ready to learn about error handling? Let's go to Error Handling! π¨