Rate Limiting (API)¶
Public-facing rate limits applied to authentication endpoints. For the implementation details (middleware, Redis keys, IP blocking, suspicious-activity heuristics), see Security → Rate Limiting.
Per-endpoint limits¶
Each limit is per client IP and applies to the API endpoint listed. Counters reset on a sliding window backed by Redis.
| Endpoint | Limit | Window | Source |
|---|---|---|---|
POST /api/v1/auth/register |
5 | 1 hour | RateLimiter.RegisterLimit() |
POST /api/v1/auth/login |
10 | 15 minutes | RateLimiter.LoginLimit() |
POST /api/v1/auth/google-login |
10 | 15 minutes | RateLimiter.LoginLimit() |
POST /api/v1/auth/line-login |
10 | 15 minutes | RateLimiter.LoginLimit() |
POST /api/v1/auth/apple-login |
10 | 15 minutes | RateLimiter.LoginLimit() |
POST /api/v1/auth/webauthn/login/begin |
10 | 15 minutes | RateLimiter.LoginLimit() |
POST /api/v1/auth/webauthn/login/finish |
10 | 15 minutes | RateLimiter.LoginLimit() |
POST /api/v1/auth/otp/send |
5 | 1 hour | RateLimiter.RegisterLimit() |
POST /api/v1/auth/otp/verify |
10 | 15 minutes | RateLimiter.LoginLimit() |
POST /api/v1/auth/reset-password |
5 | 1 hour | RateLimiter.ResetPasswordLimit() |
Every route under /api/v1/* (baseline) |
600 | 1 minute | RateLimiter.APILimit() |
These are the values defined in backend/internal/middleware/rate_limiter.go. They match the per-IP counters; multiple users behind one NAT share a counter.
How the limits stack
APILimit() is wired on the /api/v1 group, so every API request counts against the 600/min/IP baseline. Targeted limits (register, login, reset-password) are layered on top of the baseline for those specific endpoints — a registration attempt counts against both the 5/hour register bucket and the 600/min baseline. Routes under /ws (WebSocket) and webhook endpoints are not in the baseline.
Response when limited¶
A blocked request returns:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
{
"error": "too many login attempts, please try again later (max 10 per 15 minutes)"
}
The exact message varies per endpoint (see the ErrorMessage in RateLimitConfig). The error payload uses the legacy { "error": ... } shape today — clients should treat any 429 as rate-limited regardless of body shape.
Retry-After¶
429 responses include a Retry-After: <seconds> header set to the window duration for the limit that fired (e.g. Retry-After: 60 for the baseline, Retry-After: 900 for login, Retry-After: 3600 for register). Clients should:
- Honor the header — wait at least that many seconds before retrying.
- Surface a generic "Try again later" message to the user. Showing the exact countdown is fine.
- Not retry the same request automatically — these limits exist to defeat scripted attacks, and silent retries defeat the purpose.
IP blocking escalation¶
Repeated rate-limit violations lead to an outright IP block, not just per-route 429s. See Security → Rate Limiting for:
- The auto-block threshold (5 violations within 1 hour → 24-hour block)
- Suspicious activity heuristics (SQL-injection patterns, path traversal → 48-hour block)
- How to unblock an IP
Failure mode¶
If Redis is unavailable, the rate limiter fails open — every request is allowed through. This is intentional: a Redis outage should not lock everyone out of the API. The trade-off is that during an outage, an attacker has free reign on the auth endpoints; recover Redis quickly.