Add standard rate limit headers to PDF conversion endpoints
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:
OpenClaw Agent 2026-02-23 07:04:30 +00:00
parent 1623813c56
commit 978c3dc2d4

View file

@ -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;
}