Configured limiters
| Prefix | Limit | Keyed on | Surface |
|---|---|---|---|
rl:auth (Better Auth wrapper) | 5 / 60 s | IP | /api/auth/sign-in/* Coarse per-IP gate on sign-in endpoints |
rl:abuse:email | 5 / 1 h | sha256(email) | magic-link send + email-OTP send Email-bombing fix: caps sends per target inbox regardless of source IP |
rl:captcha:challenge | 30 / 60 s | IP | /api/captcha/challenge Prevents stockpiling of pre-solved ALTCHA challenges |
ratelimit:ai:chat | 20 / 60 s | IP | /api/ai/chat Per-IP rate limit on chat completions (in addition to per-user daily token cap) |
rl:feedback:submit | 3 / 1 h | IP | /feedback (form action) Public feedback submission — paired with honeypot for unauthenticated posts |
rl:username:check | 20 / 60 s | IP | /api/showcases/check-username Username availability probe — prevents enumeration scraping |
All limiters use Upstash Redis sliding-window via @upstash/ratelimit. When Redis is
unavailable in production they fail closed (block all requests for that prefix); in dev they fall
through with a warning so local development isn't blocked.