Confidence levels
Every API response includes a confidence field summarizing how reliable the validation is. Pick one threshold and apply it consistently in your code — that's the whole point.
The 5 labels
| Label | Score | Meaning |
|---|---|---|
verified |
0.95 | Mobile or fixed line in a regulated market with known carrier metadata. Highest trust. |
likely |
0.80 | Valid format, plausible line type, no negative signal. Most common outcome. |
uncertain |
0.55 | Ambiguous line type (premium, shared cost, UAN…) or country with low-quality numbering data. |
low |
0.20 | VoIP / disposable / burner carrier, or matched a flagged prefix. |
invalid |
0.00 | Format/parse failed or number not possible. |
The numeric score is derived from the label — use whichever feels more natural in your code (confidence === "verified" reads better than score >= 0.95).
How we compute it
The pipeline applies signals in order, each one can lower the confidence (never raise it):
-
line_typebaselinemobile/fixed_line→verifiedfixed_line_or_mobile/toll_free→likelyvoip/pager/voicemail→low- everything else →
uncertain
-
Carrier profile (DB lookup)
- Carriers seeded as known disposable VoIP (Google Voice, TextNow, Hushed, Pinger, Burner, Bandwidth, Twilio, Plivo, Vonage, …) → forces
low - Carriers we've manually overridden in admin (e.g. observed fraud) → uses the override label
- Plain observed carriers without explicit signal → no effect (we don't penalize unknowns)
- Carriers seeded as known disposable VoIP (Google Voice, TextNow, Hushed, Pinger, Burner, Bandwidth, Twilio, Plivo, Vonage, …) → forces
-
Country cap
- Some countries have notoriously inconsistent numbering plans → confidence is capped to
likelyeven if line_type would sayverified.
- Some countries have notoriously inconsistent numbering plans → confidence is capped to
-
Disposable prefix match
- If the E.164 number starts with a flagged prefix in our
disposable_phone_prefixtable → forceslow, setsis_disposable=true.
- If the E.164 number starts with a flagged prefix in our
Recommended thresholds by use case
High-trust signups (banking, KYC-light)
if result["confidence"] != "verified" or result["is_disposable"]:
reject("Need a verified mobile or landline number.")
Catches ~85% of fraud signals. Some legitimate users with fixed_line_or_mobile (US, BR…) will be likely and rejected — worth it for high-stakes contexts.
Standard signups (SaaS, e-commerce)
if result["confidence"] == "low" or result["is_disposable"]:
flag_for_review(user)
Filters out throw-away VoIP / disposable numbers without rejecting legit US/CA fixed_line_or_mobile users.
Lead enrichment / contact cleaning
if result["confidence"] in {"verified", "likely"}:
keep()
elif result["confidence"] == "uncertain":
keep_with_warning()
else:
drop()
Works for cleaning a CRM list — drop the obvious junk, keep everything plausible.
Diagnostics field
For debugging or audit logs, the response includes a diagnostics object showing why we landed on a given confidence:
"diagnostics": {
"format": { "parsed": true, "is_possible": true, "is_valid": true },
"disposable": { "is_disposable": false, "reason": null, "matched_prefix": null },
"confidence": {
"line_type_baseline": "verified",
"carrier_profile": { "matched": true, "label": "verified", "applied": false, "samples": 12 }
}
}
Read this when a user complains "why is my number flagged low?" — the answer is in there.
Don't trust marketing accuracy claims
Some phone validation APIs claim "99% accuracy". That's marketing — at format-only level (no HLR), the upper bound on accuracy is dictated by libphonenumber's metadata, which is open-source and the same for everyone. Anything above ~90-92% is implausible without true HLR queries against the network. We tell you what we know, mark what we don't, and let you decide.