Offline validation with phonenumbers
pip install phonenumbers
import phonenumbers
from phonenumbers import PhoneNumberFormat, PhoneNumberType, carrier, geocoder
def check(number: str, default_region: str = "US") -> dict:
try:
parsed = phonenumbers.parse(number, default_region)
except phonenumbers.NumberParseException:
return {"valid": False}
return {
"valid": phonenumbers.is_valid_number(parsed),
"e164": phonenumbers.format_number(parsed, PhoneNumberFormat.E164),
"international": phonenumbers.format_number(parsed, PhoneNumberFormat.INTERNATIONAL),
"mobile": phonenumbers.number_type(parsed) == PhoneNumberType.MOBILE,
"region": phonenumbers.region_code_for_number(parsed),
"location": geocoder.description_for_number(parsed, "en"),
"original_carrier": carrier.name_for_number(parsed, "en"),
}
print(check("0803 123 4567", "NG"))
# {'valid': True, 'e164': '+2348031234567', 'international': '+234 803 123 4567',
# 'mobile': True, 'region': 'NG', 'location': 'Nigeria', 'original_carrier': 'MTN'}
phonenumbers is the Python port of Google’s libphonenumber. It is the right tool for format checks and normalisation, and it runs without network access.
Adding carrier and line type with the API
The bundled carrier data only knows which network a number block was issued to, and is empty for the US and Canada. The numlookupapi API returns carrier and line type for any number:
import os
import requests
def lookup(number: str, country: str | None = None) -> dict:
params = {"country_code": country} if country else {}
res = requests.get(
f"https://api.numlookupapi.com/v1/validate/{number}",
params=params,
headers={"apikey": os.environ["NUMLOOKUP_API_KEY"]},
timeout=10,
)
res.raise_for_status()
return res.json()
data = lookup("+14158586273")
print(data["valid"], data["line_type"], data["carrier"])
# True mobile AT&T Mobility LLC
Batch lookups: requests.post("https://api.numlookupapi.com/v1/validate", json={"numbers": [...]}, headers=...) validates up to 100 numbers per call.
Django form field example
from django import forms
import phonenumbers
class SignupForm(forms.Form):
phone = forms.CharField()
def clean_phone(self):
raw = self.cleaned_data["phone"]
try:
parsed = phonenumbers.parse(raw, "US")
except phonenumbers.NumberParseException:
raise forms.ValidationError("Enter a valid phone number.")
if not phonenumbers.is_valid_number(parsed):
raise forms.ValidationError("Enter a valid phone number.")
return phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164)
Store the E.164 value and run the API lookup asynchronously (e.g. in a Celery task) if you need carrier or VoIP detection.