---
title: "Validate a list of phone numbers with Python"
url: "https://lookvalid.com/blog/tutorial/validate-phone-numbers-python-api.md"
canonical_url: "https://lookvalid.com/blog/tutorial/validate-phone-numbers-python-api"
last_updated: "2026-10-22"
type: "how-to"
summary: "Clean and check a list of phone numbers with Python requests: normalise the input, handle errors and 429s, and read the credit fields on each result."
related: ["https://lookvalid.com/products/phone.md", "https://lookvalid.com/tools/phone-number-validator.md", "https://lookvalid.com/docs/api/phone.md", "https://lookvalid.com/glossary/e164.md"]
---

# Validate a list of phone numbers with Python

Clean and check a list of phone numbers with Python requests: normalise the input, handle errors and 429s, and read the credit fields on each result.

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 `country` hint 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].

Normalise, deduplicate and validate a list of numbers

```python
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"]))
```

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

Fields in a result (captured example for +12025550143)

| 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 |

Errors arrive as an `error` object with a `code` such as `invalid_input` or `insufficient_credits`; the [error reference](https://lookvalid.com/docs/errors.md) 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.

One phone lookup

```bash
curl -X POST https://YOUR_API_HOST/v1/phone \
  -H "Authorization: Bearer $LOOKVALID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phone":"+12025550143"}'
```

Read the [phone product page](https://lookvalid.com/products/phone.md) for every field, try the [phone number validator](https://lookvalid.com/tools/phone-number-validator.md) in the browser, or begin with the [quickstart](https://lookvalid.com/docs/quickstart.md).

## Sources

1. [Requests documentation: Quickstart](https://requests.readthedocs.io/en/latest/user/quickstart/), read 2026-10-19
2. [ITU-T Recommendation E.164: The international public telecommunication numbering plan](https://www.itu.int/rec/T-REC-E.164/en), read 2026-10-19

By LookValid engineering. Published 2026-10-19, updated 2026-10-19.

## Related

- [Phone Numbering-Plan Validation API](https://lookvalid.com/products/phone.md)
- [Free phone number validator](https://lookvalid.com/tools/phone-number-validator.md)
- [Phone Numbering-Plan Validation reference](https://lookvalid.com/docs/api/phone.md)
- [What is E.164?](https://lookvalid.com/glossary/e164.md)
