Reason codes
risk.reasons lists every reason that fired, highest weight first. Each entry is { code, weight, identifier? }. The weights sum to risk.score before it is clamped to 0 to 100. Positive weights raise the score and negative weights lower it; a negative weight only ever offsets a positive one.
The set of codes is closed and stable. Weights change between policy versions, so read the weight the API sends with each reason rather than hard-coding one, and store policy_version with each decision. Branch on codes.
identifier says which input the reason concerns: email, username, phone, name, ip or context. It is absent when the reason concerns the whole assessment. context means the reason came from client_context or auth_context. The schema for these fields is in the interactive reference.
Footprint and accounts
Section titled “Footprint and accounts”| Code | Concerns | Effect | Meaning |
|---|---|---|---|
no_digital_footprint |
Whole assessment | Raises | Enough sources answered to say so, and none of the email, username or phone was found anywhere. New identities with no history are how abusive sign-ups usually arrive. |
established_accounts |
Whole assessment | Lowers | Several accounts were found across the identifiers. The identity has a history. |
new_email |
email |
Raises | Sources answered for the email and found no account and no breach record. |
disposable_email |
email |
Raises | The email’s domain, or a parent domain, is a disposable address service. summary.email.disposable carries the same fact. |
seen_before |
Whole assessment | Raises | An earlier assessment in your organisation carried the same email, username or phone. |
shared_ip |
ip |
Raises | Other assessments in your organisation used the same IP in the last 24 hours. The weight grows when many identities share it. summary.ip.seen_in_tenant carries the count. |
many_recent_signups |
Whole assessment | Raises | Several assessments in your organisation in the last 24 hours shared one of this request’s identifiers or its IP. |
prior_abuse_link |
The linked identifier | Raises | A submitted identifier is the one, or is one step from the one, that an earlier assessment’s abuse outcome marked. Outcomes reported through POST /v1/assessments/{assessment_id}/outcomes feed this reason. |
| Code | Concerns | Effect | Meaning |
|---|---|---|---|
voip_number |
phone |
Raises | More than one source reports the number as a VoIP line. summary.phone.line_type is voip. |
spam_reports |
phone |
Raises | The number carries spam reports from reliable sources, or from more than one source. summary.phone.report_count carries the highest count. |
fraud_flag |
phone |
Raises | More than one source flags the number for fraud. |
identity_mismatch |
phone |
None | A name reported for the number by caller-ID users disagrees with the submitted name. Recorded at no weight in the current policy. summary.consistency.name carries the comparison. |
Breach
Section titled “Breach”The breach reasons read the breach facts under summary.email.breach and summary.username.breach and the consistency results. A result that rests only on a truncated or too-common answer fires at half weight.
| Code | Concerns | Effect | Meaning |
|---|---|---|---|
aged_identity |
email |
Lowers | The email’s oldest dated breach table is years old and a site breach matches: the address has existed for a long time. Older tables lower the score more. |
breach_name_match |
name |
Lowers | The submitted name agrees with names found next to the identifiers in breach records. summary.consistency.name is match. |
breach_cooccurrence |
phone |
Lowers | The submitted phone appears next to the email in breach records. summary.consistency.phone is match. |
breach_name_mismatch |
name |
Raises | The submitted name disagrees with the names found in breach records. summary.consistency.name is mismatch. The same evidence as the consistency fact; do not count it twice. |
breach_phone_mismatch |
phone |
Raises | The submitted phone disagrees with numbers found next to the email in breach records. summary.consistency.phone is mismatch. |
recent_password_exposure |
email or username |
Raises | A plaintext password sat next to the identifier in a table dated two years ago or less, or the identifier is in a stealer log. summary.<identifier>.breach.recent_password_exposure is true. |
credential_list_exposure |
email or username |
Raises | The identifier is in a credential list or a stealer log. summary.<identifier>.breach.in_credential_lists is true. |
abuse_ecosystem_member |
email or username |
Raises | The identifier appears in a hacking-forum or abuse-service table, or in a dark-web site’s table. The weight grows with the number of such tables. |
no_breach_history |
email |
None | The email is in no breach table, the lookup was complete, and new_email or no_digital_footprint also fired. Recorded at no weight. |
IP and country
Section titled “IP and country”| Code | Concerns | Effect | Meaning |
|---|---|---|---|
proxy_ip |
ip |
Raises | The address is a VPN, proxy, Tor exit or hosting address. summary.ip.type carries the type. |
country_mismatch |
ip |
Raises | The IP’s country disagrees with policy_context.region, or with the phone’s country. summary.consistency.country is mismatch. |
Client context
Section titled “Client context”These reasons come from client_context. A client can forge every value, so they carry low weights and corroborate the identifier evidence.
| Code | Concerns | Effect | Meaning |
|---|---|---|---|
automation_fast_submit |
context |
Raises | form_duration_ms was under three seconds. |
bot_or_automation |
context |
Raises | The parsed user_agent looks like an automation tool rather than a browser or app. |
client_clock_skew |
context |
Raises | client_observed_at is far from the time the request arrived. |
context_timezone_mismatch |
context |
Raises | The region implied by timezone disagrees with the IP and phone countries. |
context_locale_mismatch |
context |
Raises | The region subtag of locale disagrees with the IP and phone countries. |
context_line_country_mismatch |
context |
Raises | The phone’s country, or the IP’s when there is no phone, disagrees with policy_context.region, and country_mismatch did not already record it. |
Auth session
Section titled “Auth session”These reasons come from auth_context. They fire only when verified is true and transport is backend; a browser-sourced or unverified context is inert.
| Code | Concerns | Effect | Meaning |
|---|---|---|---|
auth_email_verified |
context |
Lowers | Your auth provider asserts the email was verified. |
auth_phone_verified |
context |
Lowers | Your auth provider asserts the phone was verified. |
auth_mfa_recent |
context |
Lowers | mfa.present is true and mfa.age_seconds is within the recent window. |
auth_new_session |
context |
Raises | session_created_at is very recent. |
auth_new_account |
context |
Raises | account_created_at is very recent. |
auth_impersonated |
context |
Raises | impersonated is true: the session acts on behalf of another subject. |
Reading reasons in code
Section titled “Reading reasons in code”"reasons": [ { "code": "voip_number", "weight": 25, "identifier": "phone" }, { "code": "breach_name_match", "weight": -15, "identifier": "name" }]Collect the codes into a set and test membership. The assessment object page describes the summary facts each reason refers to, and the errors page describes what to do when no reasons arrive at all.