Per-key limit
| Scope | Limit | Window |
|---|---|---|
| Per API key | Varies (default 120) | 60 seconds |
Per-key overrides are stored in api_keys.rate_limit_per_minute.
Self-serve keys are issued with a fixed default of 30 requests per minute and a 60 day expiry. The request workflow has separate abuse limits: initial submissions are throttled by salted IP hash (5/hour) and private email hash (3/day), verification attempts are throttled by salted IP hash (20/10 minutes) and token hash (5/10 minutes), and successful issuance allows one self-serve key creation per salted IP hash per 24 hours.
When the per-key limiter is exceeded, the API returns 429 Too Many Requests:
{
"error": "Rate limit exceeded"
}Rate-limited responses include the retry delay in the HTTP Retry-After header when the worker can compute one.
POST /api/feedback also has a form-specific limiter. Its 429 body is { "error": "Too many submissions. Please wait a few minutes." }, and it should be handled as a local submission throttle rather than as a public API quota response. If the feedback limiter's D1 dependency is unavailable, the endpoint returns 503 Service Unavailable with { "error": "Feedback service temporarily unavailable. Please try again." } and Retry-After: 60.
API-key authentication and per-key limiter storage normally rely on D1. For protected cacheable GET edge-cache hits, the worker can serve a recently verified non-self-serve key through a bounded isolate-local auth/limiter path. It can also continue serving a recently verified non-self-serve key during a brief D1 outage by reusing its bounded verified-key cache and isolate-local limiter. Self-serve keys are refused when their D1 lookup is unavailable because revocation and claim state cannot be rechecked from stale isolate cache. Unknown or not-yet-verified keys still fail closed with 503 Service Unavailable, { "error": "Public API temporarily unavailable" }, and Retry-After: 60. Best-effort API-key usage timestamp updates do not fail otherwise successful reads.