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