Document rate limit headers in OpenAPI spec
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled

- Add reusable header components (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After)
- Reference headers in 200 responses on all conversion and demo endpoints
- Add Retry-After header to 429 responses
- Update Rate Limits section in API description to mention response headers
- Add comprehensive tests for header documentation (21 new tests)
- All 809 tests passing
This commit is contained in:
OpenClaw Subagent 2026-03-18 11:06:22 +01:00
parent a3bba8f0d5
commit 70eb6908e3
18 changed files with 801 additions and 821 deletions

24
dist/services/db.js vendored
View file

@ -1,6 +1,6 @@
import pg from "pg";
import logger from "./logger.js";
import { isTransientError } from "../utils/errors.js";
import { isTransientError, errorMessage, errorCode } from "../utils/errors.js";
const { Pool } = pg;
const pool = new Pool({
host: process.env.DATABASE_HOST || "172.17.0.1",
@ -51,7 +51,7 @@ export async function queryWithRetry(queryText, params, maxRetries = 3) {
throw err;
}
const delayMs = Math.min(1000 * Math.pow(2, attempt), 5000); // 1s, 2s, 4s (capped at 5s)
logger.warn({ err: err.message, code: err.code, attempt: attempt + 1, maxRetries, delayMs }, "Transient DB error, destroying bad connection and retrying...");
logger.warn({ err: errorMessage(err), code: errorCode(err), attempt: attempt + 1, maxRetries, delayMs }, "Transient DB error, destroying bad connection and retrying...");
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
@ -81,7 +81,7 @@ export async function connectWithRetry(maxRetries = 3) {
throw validationErr;
}
const delayMs = Math.min(1000 * Math.pow(2, attempt), 5000);
logger.warn({ err: validationErr.message, code: validationErr.code, attempt: attempt + 1 }, "Connection validation failed, destroying and retrying...");
logger.warn({ err: errorMessage(validationErr), code: errorCode(validationErr), attempt: attempt + 1 }, "Connection validation failed, destroying and retrying...");
await new Promise(resolve => setTimeout(resolve, delayMs));
continue;
}
@ -93,7 +93,7 @@ export async function connectWithRetry(maxRetries = 3) {
throw err;
}
const delayMs = Math.min(1000 * Math.pow(2, attempt), 5000);
logger.warn({ err: err.message, code: err.code, attempt: attempt + 1, maxRetries, delayMs }, "Transient DB connect error, retrying...");
logger.warn({ err: errorMessage(err), code: errorCode(err), attempt: attempt + 1, maxRetries, delayMs }, "Transient DB connect error, retrying...");
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
@ -153,28 +153,18 @@ export async function initDatabase() {
* - Orphaned usage rows (key no longer exists)
*/
export async function cleanupStaleData() {
const results = { expiredVerifications: 0, staleKeys: 0, orphanedUsage: 0 };
const results = { expiredVerifications: 0, orphanedUsage: 0 };
// 1. Delete expired pending verifications
const pv = await queryWithRetry("DELETE FROM pending_verifications WHERE expires_at < NOW() RETURNING email");
results.expiredVerifications = pv.rowCount || 0;
// 2. Delete unverified free-tier keys (email not in verified verifications)
const sk = await queryWithRetry(`
DELETE FROM api_keys
WHERE tier = 'free'
AND email NOT IN (
SELECT DISTINCT email FROM verifications WHERE verified_at IS NOT NULL
)
RETURNING key
`);
results.staleKeys = sk.rowCount || 0;
// 3. Delete orphaned usage rows
// 2. Delete orphaned usage rows (key no longer exists in api_keys)
const ou = await queryWithRetry(`
DELETE FROM usage
WHERE key NOT IN (SELECT key FROM api_keys)
RETURNING key
`);
results.orphanedUsage = ou.rowCount || 0;
logger.info({ ...results }, `Database cleanup complete: ${results.expiredVerifications} expired verifications, ${results.staleKeys} stale keys, ${results.orphanedUsage} orphaned usage rows removed`);
logger.info({ ...results }, `Database cleanup complete: ${results.expiredVerifications} expired verifications, ${results.orphanedUsage} orphaned usage rows removed`);
return results;
}
export { pool };