If an endpoint costs you money, sends email, creates data, calls an AI model, starts a job or hits a third-party API, it needs abuse protection.
Vibe-coded apps often miss this because rate limiting is invisible in the happy path.
The app works fine when you submit the form once.
The question is what happens when someone submits it 5,000 times.
What needs rate limiting
Start with routes that do any of these:
- call OpenAI, Anthropic or another AI provider
- send email
- create checkout sessions
- create accounts
- submit contact forms
- upload files
- trigger scans, jobs or crawls
- query expensive database views
- reset passwords
- send magic links
- post support tickets
- scrape or fetch third-party URLs
These routes can create cost, spam, database load or account abuse.
Find sensitive routes
Search the code:
rg "openai|anthropic|stripe|resend|sendEmail|checkout|upload|scan|webhook"
rg "export async function POST|export async function GET" app
Then ask your agent:
Find every route that can be abused before launch.
Flag routes that:
- call paid APIs
- send email
- create database records
- start background jobs
- accept user-generated input
- expose private data
- can be called without authentication
For each route, recommend whether it needs rate limiting by IP, user, domain, email address, or a combination.
Choose the limit by risk
Not every endpoint needs the same limit.
A public newsletter form might allow a few attempts per IP per hour.
A password reset endpoint might limit by IP and email address.
An AI generation endpoint might limit by authenticated user, subscription tier and IP.
A scan-start endpoint might limit by domain, account and payment status.
Use this prompt:
Design rate limits for these endpoints.
For each endpoint, specify:
- key to limit on: IP, user ID, email, domain, or account
- window size
- max attempts
- response status and message
- what to log
- what not to log
- whether admins or webhooks should bypass it
Do not leak too much in errors
Rate limit responses should be useful without helping attackers.
Good:
Too many requests. Please wait a few minutes and try again.
Risky:
This email has requested 6 password resets in 15 minutes.
The second message confirms account activity.
Ask:
Review rate limit error messages.
Make sure they are clear for real users but do not reveal account existence, internal thresholds, payment status, or security-sensitive details.
Add server-side validation too
Rate limiting is not validation.
You still need to validate:
- input shape
- maximum lengths
- allowed domains
- file type and size
- authentication
- authorisation
- payment or entitlement
Rate limiting slows abuse. Validation rejects bad requests.
Use this prompt:
Review this endpoint for both validation and rate limiting.
Separate:
- malformed input
- unauthenticated user
- authenticated but not authorised
- over quota
- suspected abuse
- third-party provider failure
Recommend the correct response for each case.
Watch for client-only protection
Disabling a button after click is good UX.
It is not rate limiting.
Anything enforced only in the browser can be bypassed.
Protection belongs on the server route that receives the request.
Ask:
Find any endpoint where abuse prevention only exists in client-side code.
Move the protection to the server-side route and keep the client-side disabled state only as UX polish.
What PageLens can help with
PageLens cannot prove every backend route is rate-limited from outside the codebase.
But it can catch public launch-readiness signals that often travel with missing abuse protection: exposed forms, weak security posture, missing trust signals, and pages that invite automated interaction without enough context.
Use PageLens alongside source review.
If your app has AI credits, email sending, payments, uploads or public forms, rate limiting is not optional.