Tutorials
Validate a list of phone numbers with Python
This tutorial takes a messy list of phone numbers, tidies it, and checks each one with the LookValid phone product using Python and requests. It shows how to read the credit fields, handle errors and rate limits, and what the result does and does not tell you.
What the phone check answers
The phone product checks a number against its country's numbering plan and returns the number in E.164, national and international formats, the country, the number type the plan assigns, and for North American numbers the area code and its region. It never contacts a carrier, so it cannot say whether a number is switched on, ported or reachable.
E.164 is the ITU-T recommendation titled "The international public telecommunication numbering plan" 2, and it is the form to store: one unambiguous string per number. A number the plan rejects comes back as found: false with the reason, and costs nothing.
Step 1: normalise the input
The API reads punctuation and a trailing extension on its own, so normalisation here is about hygiene: strip the characters that cause stray rejections, collapse whitespace, and drop duplicates so you do not pay twice for the same number. Letters in the number body are refused by the API, so keep letters out of the cleaned string except for an extension marker.
- Replace non-breaking spaces and runs of whitespace with a single space.
- Remove brackets, dots and dashes between digits.
- Remove spaces between digits, so two spellings of one number become the same string.
- Keep a leading plus sign: it tells the parser the country code is already there.
- Pass a two-letter
countryhint such as US for numbers written without a country code. Without it the check assumes US.
Step 2: call the API from Python
With requests, passing a dict to the json argument encodes the body and sets the content type for you 1. The docs also advise setting timeout on nearly every production request, since without it a call can hang indefinitely 1. Check status_code yourself, because a 4xx or 5xx response is returned rather than raised unless you call raise_for_status() 1.
import os
import re
import time
import requests
API = "https://YOUR_API_HOST"
def normalise(raw):
text = raw.replace("\u00a0", " ")
text = re.sub(r"[().-]", "", text)
text = re.sub(r"(?<=\d)\s+(?=\d)", "", text)
return re.sub(r"\s+", " ", text).strip()
def lookup(session, phone, country=None):
payload = {"phone": phone}
if country:
payload["country"] = country
for attempt in range(3):
r = session.post(API + "/v1/phone", json=payload, timeout=10)
if r.status_code == 429 and attempt < 2:
time.sleep(int(r.headers.get("Retry-After", "1")))
continue
if r.status_code >= 400:
err = r.json().get("error", {})
raise RuntimeError(f"{err.get('code')}: {err.get('message')}")
return r.json()
def validate(numbers, country="US"):
session = requests.Session()
session.headers["Authorization"] = "Bearer " + os.environ["LOOKVALID_API_KEY"]
seen, results, spent = set(), {}, 0
for raw in numbers:
phone = normalise(raw)
if not phone or phone.lower() in seen:
continue
seen.add(phone.lower())
body = lookup(session, phone, country)
spent += body["credits_charged"]
results[phone] = body["data"]["e164"] if body["found"] else body["reason"]
return results, spent
print(validate(["+1 (202) 555-0143", "+1 202.555.0143"]))
Normalise, deduplicate and validate a list of numbers
A found number is billed at 1 credit per the catalog, and the loop above skips the second spelling of the same number before it reaches the API.
Step 3: read the result and the credit fields
| Field | Example | Notes |
|---|---|---|
| data.e164 | +12025550143 | Store this form |
| data.number_type | fixed_line_or_mobile | From the numbering plan, not a carrier |
| data.area_code_region.name | District of Columbia | North American numbers only |
| data.carrier | null | Always null: no carrier data is used |
| credits_charged | per catalog | Also in the X-Credits-Charged header |
| credits_remaining | your balance | Also in the X-Credits-Remaining header |
| cache_hit | true or false | True when the same lookup was already paid for within the cache window |
Fields in a result (captured example for +12025550143)
Errors arrive as an error object with a code such as invalid_input or insufficient_credits; the error reference lists them all. Stop the run on insufficient_credits rather than retrying.
Try it on three numbers
Test one number with curl before you run a list.
curl -X POST https://YOUR_API_HOST/v1/phone \
-H "Authorization: Bearer $LOOKVALID_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone":"+12025550143"}'
One phone lookup
Read the phone product page for every field, try the phone number validator in the browser, or begin with the quickstart.
Sources
- Requests documentation: Quickstart, read 2026-10-19
- ITU-T Recommendation E.164: The international public telecommunication numbering plan, read 2026-10-19