Rate Limits & Abuse
We have 3 layers of rate limiting, not 1
Most APIs say "rate limited" and don't explain how. We'll tell you exactly what happens at each layer.
Layer 1: Per-minute rate limit (per tier)
What it does: Redis atomic operation, enforces per-tier rate limits per minute.
Example: Bot making 100 requests/second → Blocked after 10, doesn't hit Redis or database.
Error response:
429 Too Many Requests
{
"error": "Rate limit exceeded",
"code": "RATE_LIMIT_MEMORY",
"limit": 10,
"window": "1 minute",
"resetIn": 45,
"hint": "You're making requests too quickly. Slow down."
}Layer 2: Behavioral throttling — Slows down burst traffic
What it does: If you make ≥30 requests/minute, we add delays to slow you down (protect infrastructure).
Example: You make 45 requests in 1 minute → Requests still succeed but each waits 2s.
Layer 3: Monthly quota (Redis-cached) — Prevent overage charges
What it does: Enforces monthly request limits per tier. Cached in Redis for instant checks (NO database queries!).
Example: Free user hits 1,000/1,000 requests on Jan 25 → Blocked until Feb 1.
Error response:
403 Forbidden
{
"error": "Monthly quota exceeded",
"code": "QUOTA_EXCEEDED",
"tier": "free",
"used": 1000,
"limit": 1000,
"resetAt": "2024-02-01T00:00:00Z",
"resetIn": 518400,
"upgrade": {
"url": "/dashboard/billing/upgrade",
"nextTier": "pro",
"nextLimit": 50000
}
}Chaos Protection — Ban escalation for persistent abuse
What it does: Tracks violations (rate limit hits, invalid requests) and automatically escalates bans for repeat offenders.
Why this matters: Makes persistent attacks EXPENSIVE. Bots can't just retry forever—each violation makes the next ban exponentially longer.
Permanent bans: Stored in database, enforced across all API endpoints, requires manual review to lift.
Layer 3: Database quota (20-50ms) — Monthly totals
What it does: Supabase query, checks total requests this billing cycle.
Example: Free user hits 1,000/1,000 requests on Jan 25 → Blocked until Feb 1.
Error response:
403 Forbidden
{
"error": "Monthly quota exceeded",
"code": "QUOTA_EXCEEDED",
"tier": "free",
"used": 1000,
"limit": 1000,
"billingCycle": {
"start": "2024-01-01T00:00:00Z",
"end": "2024-02-01T00:00:00Z",
"resetIn": 518400
},
"hint": "Upgrade to Pro for 50,000 requests/month or wait for reset",
"upgrade": {
"url": "/dashboard/billing/upgrade"
}
}How requests are counted
1 API call = 1 request, regardless of operation:
Important: Batch operations count as one request, regardless of file count. This is intentional—it encourages efficient API usage.
Limits on batch size:
- Free: 10 files per batch
- Pro: 100 files per batch
- Enterprise: 10,000 files per batch
What happens when you hit a limit
Dashboard banner: Shows usage with upgrade prompt
Dashboard: Red banner with countdown
Response includes upgrade link and reset time
No surprise charges — just blocked until upgrade or reset
Abuse detection and prevention
Beyond rate limits, we track abuse events:
- Rate limit exceeded (any layer)
- Invalid credentials submitted repeatedly
- Disposable email detected
- Quota exceeded
- Verification spam (clicking "verify" 100× in 1 minute)
- Domain creation spam
Automatic actions:
False positives: If you think you were suspended incorrectly, email support@obitox.com with:
- Your API key (first 8 characters, e.g., ox_196ae...)
- What you were trying to do
- Any error messages/request IDs you received
We'll review logs and unban if it was a mistake. Most bans are correct (sorry, bots).
How to avoid hitting limits
resetIn seconds before retrying.if (response.status === 429) {
const data = await response.json();
await sleep(data.resetIn * 1000);
return retry();
}Cloudflare layer (Layer 0)
Before requests even hit our API, Cloudflare provides:
- DDoS protection — Blocks 182 billion threats/day globally
- Bot detection — Challenge Score / Turnstile
- IP reputation — Known bad IPs blocked automatically
- L7 rate limiting — 1,000 requests/min per IP
This catches ~90% of attacks before they reach our code. If you're a legitimate user, you'll never notice it. If you're a bot, you'll never get through.
Response headers (for monitoring)
Every successful request includes rate limit headers:
X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 847 X-RateLimit-Reset: 1704675600 X-RateLimit-Tier: pro
Use these to monitor usage in your application:
const response = await fetch(...);
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
if (remaining < 100) {
console.warn('Running low on requests:', remaining);
// Maybe slow down, notify admins, etc.
}