Risk scores and reasons
How scores, levels and verdicts work, and every reason code we return with its impact and meaning.
Every check returns the same risk block:
"risk": {
"score": 64,
"level": "medium",
"verdict": "review",
"reasons": [
{ "code": "domain_brand_new", "impact": 55, "message": "acme-trial.io was registered 3 days ago." },
{ "code": "role_account", "impact": 15, "message": "info@ is usually a shared team inbox, not a person." }
]
}
Score, level and verdict
| Score | Level | Verdict | Suggested action |
|---|---|---|---|
| 0–29 | low |
admit |
Let them in. |
| 30–69 | medium |
review |
Let them in with friction: email or phone verification, or hold rewards until verified. |
| 70–100 | high |
refuse |
Block, or ask for different details. |
Impact is how strongly one reason, on its own, points to a fake or unusable signup. Reasons combine like independent odds: score = 100 × (1 − Π(1 − impact/100)).
Codes are stable and safe to branch on. New codes may be added over time, so treat unknown codes by their impact.
Email reasons
| Code | Impact | Meaning |
|---|---|---|
invalid_syntax |
100 | Not a valid email address. |
domain_not_found |
95 | The domain doesn't exist. |
domain_refuses_mail |
95 | The domain publishes a null MX: it accepts no email. |
mailbox_not_found |
95 | The mail server says this mailbox doesn't exist (mailbox verification only). |
disposable_domain |
90 | A throwaway inbox provider. |
no_mail_server |
90 | The domain has no mail server. |
mx_shared_with_disposable |
75 | The domain's mail server also serves known throwaway domains. Catches new throwaway brands. |
domain_brand_new |
55 | Registered less than 7 days ago. |
domain_new |
35 | Registered less than 30 days ago. |
possible_typo |
30 | Looks like a misspelling of a popular provider, such as gmial.com. |
catch_all_domain |
20 | The server accepts mail for any address, so the mailbox can't be confirmed. |
role_account |
15 | A shared inbox, like info@ or support@. |
random_local_part |
12 | The part before the @ looks machine-generated. |
dns_unavailable |
10 | DNS couldn't be reached, so mail servers are unknown. |
no_email_authentication |
8 | The domain publishes neither SPF nor DMARC. |
plus_addressed |
5 | The address uses a +tag. |
IP reasons
| Code | Impact | Meaning |
|---|---|---|
invalid_ip |
100 | Not a valid IPv4 or IPv6 address. |
tor_exit_node |
85 | A current Tor exit node. |
vpn_network |
60 | A hosting network mainly used by commercial VPNs. |
datacenter_ip |
45–55 | A cloud or hosting provider, not a home or mobile network. |
non_public_ip |
40 | A private or reserved address: you're probably sending your own proxy's IP. |
privacy_relay |
20 | Traffic relayed through a CDN or privacy service. |
Phone reasons
| Code | Impact | Meaning |
|---|---|---|
unparseable_number |
100 | Not a phone number, or missing its country code. |
invalid_number |
85–95 | Wrong length, or an unassigned range. |
premium_rate_number |
80 | Premium-rate line. |
toll_free_number |
60 | Toll-free business line. |
pager_number, voicemail_number |
60 | Can't receive SMS codes. |
voip_number |
50 | VoIP range. |
shared_cost_number |
40 | Business line. |
personal_number, uan_number |
30 | Forwarding or switchboard number. |
country_mismatch |
15 | The number's country differs from the one you passed. |
Domain reasons
The domain check uses the same codes as the email check, with lower impact for "doesn't receive mail" (a website domain may legitimately not use email): domain_refuses_mail 60, no_mail_server 50.
Signup cross-checks
| Code | Impact | Meaning |
|---|---|---|
automation_pattern |
40 | Throwaway email from a datacenter or Tor network. |
phone_ip_country_mismatch |
15 | Phone country differs from IP country. |