First I killed the signup wall. Then the bots came.
So I killed them too.
Part 1: The wall
Three weeks ago I removed the signup wall from my API product. Instead
of email → password → verification code → API key, visitors landed on
a free playground. 15 models. 10 requests/day. No login. No email.
No friction.
It worked. Registrations jumped from 1 per week to 14. 86% of new
signups were real Gmail accounts. Conversion from playground to
registration hit 19%.
Then something else showed up.
Part 2: The bots
I checked the server logs and found /auth/send-verification-code
getting hammered. 2,600 requests per day. Every single day.
Jul 17: 2,634
Jul 18: 2,713
Jul 19: 2,642
Jul 20: 2,550
Jul 21: 2,745
That's 47-60% of all server traffic. The bots were hitting the
verification endpoint with disposable emails, rotating IPs, never
actually completing registration. Just burning CPU cycles and
wasting bandwidth.
At first I thought "who cares, they're not getting through." But
2,600 requests/day isn't free. On a 1-core VPS, that's real load.
And it makes real traffic analysis impossible when half your data
is noise.
Part 3: The fix (5 lines)
# Before: no rate limit
@app.post("/auth/send-verification-code")
async def send_verification_code(request: Request):
email = body.get("email")
# generate code, send email...
# After: one check
@app.post("/auth/send-verification-code")
async def send_verification_code(request: Request):
client_ip = request.client.host
now = time.time()
with _verify_code_rate_lock:
last_req = _verify_code_rate_limits.get(client_ip, 0)
if now - last_req < 60:
raise HTTPException(429, "Too many requests")
_verify_code_rate_limits[client_ip] = now
email = body.get("email")
# generate code, send email...
That's it. A dictionary. A lock. A timestamp check. 1 request per
minute per IP. No Redis. No external rate limiter. No database.
Five lines of in-memory Python.
I also added a domain blocklist. Ten seconds of grep showed the top
abusers: disposable email domains like mailinator, guerrillamail,
and a few others. Blocked at registration.
Part 4: The result
| Week | Verification requests/day | Server load |
|---|---|---|
| Jul 17-23 (before) | ~2,600 | 47-60% bot traffic |
| Jul 24-31 (rate limit deployed) | ~2,500 | bot slowed but IPs rotated |
| Aug 1-4 (rate limit + domain block) | ~1,600 → 0 | decay then silence |
| Aug 5-9 (now) | 0-2 | bot traffic eliminated |
From 17,000 bot requests per week to zero. Five lines of code. One
domain blocklist.
The pattern
This is the second time I've learned the same lesson: you don't need
a complex solution for a simple problem.
Removing the signup wall wasn't a multi-month redesign. It was one
static HTML page with an 80-line FastAPI endpoint that bypassed auth.
Killing the bots wasn't a rate-limiting service or a CAPTCHA
integration. It was five lines of in-memory rate limiting and a set
of blocked domains.
Both times the fix was simpler than the problem. Both times the
impact was immediate and measurable.
The most effective infrastructure changes I've made to this product
weren't the big features. They were the small, targeted interventions
that removed friction for real users and added friction for bad ones.
AIBridge — 15 Chinese AI models behind one OpenAI-compatible endpoint.
Free playground with no signup. GitHub OAuth login in one click. No
bots wasting your bandwidth (or ours).
→ aibridge-api.com/playground.html (15 models, no signup)
→ aibridge-api.com/prompts.html (24 ready-to-use prompts)



Top comments (0)