Documentation
Security
Your own anti-abuse rules on top of the platform limits: per-IP and per-app rate limits, recipient spread, IP checks, daily caps and IP lists.
The panel’s Security page holds your own anti-abuse rules, on top of the limits we run for everyone. They can only tighten: nothing here loosens a platform limit, and a rule you never touch costs nothing. New apps start with the app rate limit and the per-IP rate limit on; existing apps are unchanged until you turn a rule on.
Three modes
Every rule runs in one of three modes.
| Mode | What happens |
|---|---|
| Off | The rule is not evaluated. |
| Monitor | The rule is evaluated; every hit is counted under “Would refuse” and listed in Recent events, but the send goes out and no email is sent. Use it to see what a limit would do before it does it. |
| Enforce | The send is refused. Your backend gets a 429 or 403 (see below) and the message is never sent or billed. |
Start a rule in Monitor, watch Recent events for a day, then switch to Enforce. “Apply recommended” fills in values sized for a login form; adjust to your traffic before saving.
The rules
| Rule | What it does |
|---|---|
| App rate limit | How many codes the whole app may start per minute and per hour. Stops a burst from many addresses at once. On for every new app (60 a minute, 1,000 an hour); raise it before you grow past that. |
| Per-IP rate limit | Codes one end user’s IP may start per minute and per hour, whatever its reputation. On for every new app (5 a minute, 30 an hour). Needs client_ip on the send. |
| Per-IP recipient spread | How many different phone numbers or emails one IP may reach in an hour. A real user reaches one or two; a pump reaches a new number every time. Three is a good value. |
Require client_ip | Refuse a send that does not carry the end user’s IP. Turn it on once every send does, so an integration mistake cannot switch the IP rules off. |
| Block VPN, proxy and Tor / Block datacenter IPs | Refuse end users behind an anonymising network or on a cloud address, which real phones rarely use. The platform already slows both down to one code a minute; this refuses them. |
| Per-recipient limit | Tighter than the platform limit for one number or email: codes per hour and seconds between two codes. Both limits apply; the stricter wins. |
| Daily caps | A ceiling per UTC day for this app: sends, spend in USD, or both. 0 switches that half off. The hard backstop if everything else is tuned wrong. |
| IP lists | One IP or CIDR range per line. A denied address is always refused. An allowed address skips the IP rules above, and only those; the app-wide and recipient rules still apply. |
What your backend sees
| Status | Error | Meaning |
|---|---|---|
429 | RateLimitExceededError | A counting rule is full (app, IP, spread, recipient, daily). The message ends in policy; platform limits name their bucket instead (recipient, company, key, cooldown, client_ip). Honour Retry-After and let the user try again. |
403 | SendPolicyBlockedError | An absolute rule refused the send: a deny-listed IP, a missing client_ip, or a blocked IP type. Do not retry the same request. |
The response never names the rule. You configured it, so the Recent events table on the Security page tells you which one fired, with the IP, the country and the masked recipient; a caller holding a leaked key learns nothing. See Error codes.
Three things to know
- The IP rules only see sends that carry
client_ip. Pass the end user’s address inPOST /otp/send; the page shows what share of your last seven days did. - Every hit is kept for 30 days, refused or monitored, so a limit can be tuned against what actually happened rather than guessed.
- Owners get one email per app per day, on the first refusal, when the switch at the bottom of the page is on. Country restrictions are not here: they live on Routes (see Channels & routing).