Denialbase enforces rate limits at two layers — Cloud Armor at the edge (per-IP) and Rack::Attack at the application (per-user). Both return
429 Too Many Requests when exceeded, with a Retry-After header telling you how long to wait.Application-level limits (per authenticated user)
| Endpoint group | Limit | Window |
|---|---|---|
| General reads | 300 | 1 minute |
| General writes | 60 | 1 minute |
| Auth (login, password reset) | 5 | 20 seconds |
| 2FA setup | 5 | 1 minute |
| Passkey operations | 10 | 1 minute |
| File uploads | 30 | 1 minute |
| Email sending (per target address) | 3 | 5 minutes |
| Token verification (magic links) | 10 | 1 minute |
| Data exports | 10 | 1 hour |
Edge limits (per IP)
| Environment | Threshold | Action |
|---|---|---|
| Staging | 500 req/min | 5-minute 429 ban |
| Production | 1000 req/min | 5-minute 429 ban |
Response headers
When a rate limit is in effect or hit, we return:| Header | Meaning |
|---|---|
X-RateLimit-Limit | The limit that applies |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
Retry-After | Seconds to wait (only on 429) |
Retry strategy
On 429
Sleep for
Retry-After seconds, then retry. Don’t ignore the header — ignoring it will get you edge-banned for 5 minutes.Use idempotency keys for writes
See Errors → Idempotency.
Best practices
- Cache read responses where appropriate;
GETresponses includeETagheaders for conditional requests (If-None-Match). - Batch where batch endpoints exist — a single call to
POST /api/v1/documents/bulkis better than 50 serial uploads. - Webhook for event notifications rather than polling (see Webhooks).
- Paginate list endpoints — don’t pull more than you need. Default page size is 25; max is 100.