Reason codes

The reason field in every response carries a single machine-readable code explaining the verdict. Use it for branching logic and analytics; never parse the human-readable message field.

Positive

Reason Description
valid Number is well-formed and passes all checks. Use confidence to grade it.

Format / parsing failures

Reason Description
invalid_format The string doesn't look like a phone number at all. Garbage input, letters, too short, etc.
not_possible Parses as a phone, but isPossibleNumber=false — wrong length for the country's numbering plan. E.g. 6 digits in France.
invalid_country The country parameter you sent is not a valid ISO-3166 alpha-2 code, or libphonenumber doesn't have data for it.
country_required You sent a non-E.164 number (no leading +) without a country parameter. We can't disambiguate 0612345678 between FR / BE / NL on its own.

Line-type signals (number is valid but flagged)

Reason Description
voip Generic VoIP line (without disposable signal). Real but lower trust for SMS / fraud scoring.
toll_free Toll-free number (1-800 / 0800). Usable but not a personal mobile.
premium_rate Premium-rate / surcharged number. Beware of charging users to call these.
shared_cost Shared-cost number (split between caller and callee).
personal_number Personal redirect (UAN-like). Trust is lower because the actual destination is opaque.
pager Pager line. Legacy; almost certainly not what you want for an active user.
uan Universal Access Number (corporate switchboard). Valid but not a personal phone.
voicemail Voicemail-only line.
unknown_type libphonenumber returned unknown — we can parse the number but can't classify it. Treat as uncertain confidence.

Disposable / blacklist

Reason Description
disposable_voip Carrier matched our seed list of disposable VoIP services: Google Voice, TextNow, Hushed, Pinger, Burner, Bandwidth, Twilio, Plivo, Vonage, etc.
disposable_prefix The E.164 prefix matched our disposable_phone_prefix table — used for known burner ranges that aren't tied to a single carrier.
low_confidence_carrier Carrier is in our DB with confidence_label='low' (admin-overridden — observed fraud rate or other manual signal).

Pays / ranges

Reason Description
country_blocked Country is on a sanctions / blacklist list. (Reserved for future use; not currently emitted.)
test_range Number falls in a documented test range (e.g. UK 0163… stub numbers). (Reserved; future use.)
unallocated Prefix is not allocated by the regulator. (Reserved; future use.)

HLR (future tier — not yet active)

When we ship the level=hlr tier, these reasons become available:

Reason Description
hlr_dead HLR returned ABSENT_SUBSCRIBER — number is not active on any network.
hlr_unreachable HLR returned ROAMING but couldn't be probed reliably.
hlr_not_available Country has no HLR coverage available with our upstream provider.

Until then, level=hlr requests return 400 level_not_available.

Programmatic handling example

# Anti-spam signup pipeline
NEGATIVE = {
    "invalid_format", "not_possible", "country_required", "invalid_country",
    "disposable_voip", "disposable_prefix", "low_confidence_carrier",
    "voicemail", "pager",
}
SUSPICIOUS = {"voip", "personal_number", "uan", "shared_cost", "premium_rate", "unknown_type"}

if result["reason"] in NEGATIVE:
    return reject()
if result["reason"] in SUSPICIOUS:
    return require_email_verification_too()
return accept()

When in doubt

Combine reason + confidence + is_disposable:

  • reason=valid AND confidence=verified → green light
  • reason=valid AND is_disposable=false AND confidence in (verified, likely) → green light, but log
  • everything else → at least one red flag worth surfacing