Add standard rate limit headers to PDF conversion endpoints
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled
- Modified checkRateLimit to return RateLimitResult object with limit, remaining, and resetTime - Added X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers to ALL responses - Added Retry-After header to 429 responses - Headers now provide developers visibility into their quota usage
This commit is contained in:
parent
1623813c56
commit
978c3dc2d4
1 changed files with 32 additions and 8 deletions
|
|
@ -43,7 +43,7 @@ function getRateLimit(apiKey: string): number {
|
|||
return isProKey(apiKey) ? PRO_RATE_LIMIT : FREE_RATE_LIMIT;
|
||||
}
|
||||
|
||||
function checkRateLimit(apiKey: string): boolean {
|
||||
function checkRateLimit(apiKey: string): RateLimitResult {
|
||||
cleanupExpiredEntries();
|
||||
|
||||
const now = Date.now();
|
||||
|
|
@ -51,19 +51,35 @@ function checkRateLimit(apiKey: string): boolean {
|
|||
const entry = rateLimitStore.get(apiKey);
|
||||
|
||||
if (!entry || now >= entry.resetTime) {
|
||||
const resetTime = now + RATE_WINDOW_MS;
|
||||
rateLimitStore.set(apiKey, {
|
||||
count: 1,
|
||||
resetTime: now + RATE_WINDOW_MS
|
||||
resetTime
|
||||
});
|
||||
return true;
|
||||
return {
|
||||
allowed: true,
|
||||
limit,
|
||||
remaining: limit - 1,
|
||||
resetTime
|
||||
};
|
||||
}
|
||||
|
||||
if (entry.count >= limit) {
|
||||
return false;
|
||||
return {
|
||||
allowed: false,
|
||||
limit,
|
||||
remaining: 0,
|
||||
resetTime: entry.resetTime
|
||||
};
|
||||
}
|
||||
|
||||
entry.count++;
|
||||
return true;
|
||||
return {
|
||||
allowed: true,
|
||||
limit,
|
||||
remaining: limit - entry.count,
|
||||
resetTime: entry.resetTime
|
||||
};
|
||||
}
|
||||
|
||||
function getQueuedCountForKey(apiKey: string): number {
|
||||
|
|
@ -106,10 +122,18 @@ export function pdfRateLimitMiddleware(req: Request & { apiKeyInfo?: any }, res:
|
|||
const apiKey = keyInfo?.key || "unknown";
|
||||
|
||||
// Check rate limit first
|
||||
if (!checkRateLimit(apiKey)) {
|
||||
const limit = getRateLimit(apiKey);
|
||||
const rateLimitResult = checkRateLimit(apiKey);
|
||||
|
||||
// Set rate limit headers on ALL responses
|
||||
res.set('X-RateLimit-Limit', String(rateLimitResult.limit));
|
||||
res.set('X-RateLimit-Remaining', String(rateLimitResult.remaining));
|
||||
res.set('X-RateLimit-Reset', String(Math.ceil(rateLimitResult.resetTime / 1000)));
|
||||
|
||||
if (!rateLimitResult.allowed) {
|
||||
const tier = isProKey(apiKey) ? "pro" : "free";
|
||||
res.status(429).json({ error: `Rate limit exceeded: ${limit} PDFs/min allowed for ${tier} tier. Retry after 60s.` });
|
||||
const retryAfterSeconds = Math.ceil((rateLimitResult.resetTime - Date.now()) / 1000);
|
||||
res.set('Retry-After', String(retryAfterSeconds));
|
||||
res.status(429).json({ error: `Rate limit exceeded: ${rateLimitResult.limit} PDFs/min allowed for ${tier} tier. Retry after ${retryAfterSeconds}s.` });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue