Every communications workflow has a question it needs answered before the first message or call goes out: what kind of number is this? Is it a mobile phone, a landline, or a VoIP line? Which carrier owns it? Has it been ported? The answers determine whether you send an SMS or place a voice call, whether you trust an inbound caller or flag them for review, and whether a lead's contact info is real or stale.
The Telnyx Number Lookup API returns carrier, line type, and portability data for any phone number in a single request. This walkthrough builds a Python Flask service that wraps that API with a 24-hour in-memory cache so repeat lookups are free and fast.
The canonical code example is in the Telnyx code examples repo:
Setup
git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/phone-number-lookup-python
cp .env.example .env
pip install -r requirements.txt
Edit .env and set your API key:
TELNYX_API_KEY=KEY0123456789ABCDEF
FLASK_DEBUG=false
Start the server:
python app.py
The app runs on http://localhost:5000.
How The Code Works
Client Initialization
The Telnyx Python SDK client is created once at startup:
client = telnyx.Telnyx(api_key=os.getenv("TELNYX_API_KEY"))
The Lookup Function
The core function validates the phone number format, checks the cache, and calls the Telnyx API on a miss:
def lookup_phone_number(phone_number: str) -> dict:
if not phone_number.startswith("+"):
raise ValueError("Phone number must be in E.164 format (e.g., +15551234567)")
cached_result = get_cached_lookup(phone_number)
if cached_result:
cached_result["from_cache"] = True
return cached_result
response = client.number_lookup.retrieve(phone_number)
lookup_data = {
"phone_number": response.data.phone_number,
"country_code": response.data.country_code,
"carrier": {
"name": response.data.carrier.name if response.data.carrier else None,
"type": response.data.carrier.type if response.data.carrier else None,
},
"line_type": response.data.line_type,
"number_type": response.data.number_type,
"portability": {
"status": response.data.portability.status if response.data.portability else None,
"last_checked_at": response.data.portability.last_checked_at if response.data.portability else None,
},
"from_cache": False,
}
cache_lookup_result(phone_number, lookup_data)
return lookup_data
Caching
The cache is a Python dict with a 24-hour TTL. Each entry stores the lookup result and a timestamp. is_cache_valid() compares the stored timestamp against the TTL, and expired entries are replaced on the next lookup.
This keeps repeated lookups of the same number free — the Telnyx API is only called once per number per 24-hour window.
Error Handling
The Flask endpoints catch Telnyx SDK exceptions and return appropriate HTTP status codes:
401for invalid API keys
429for rate limit hits
503for network errors
400for malformed requests
Test It
Look up a number (POST):
curl -X POST http://localhost:5000/lookup \
-H "Content-Type: application/json" \
-d '{"phone_number": "+15551234567"}'
Look up a number (GET):
curl http://localhost:5000/lookup/+15551234567
Check the cache:
curl http://localhost:5000/cache/stats
Run the same lookup twice — the second response returns "from_cache": true.
Going to Production
This example uses an in-memory cache for simplicity. For production:
Shared cache — Replace the in-memory dict with Redis so the cache survives restarts and is shared across instances.
Authentication — Add API key validation on your endpoints so the lookup service is not open to the internet.
Rate limiting — Protect your endpoints and stay within Telnyx Number Lookup limits.
Persistence — Store lookup history in a database if you need an audit trail.
Monitoring — Add structured logging and alert on error rates.
Resources
- Code example:
- Number Lookup API Reference:
- Telnyx Portal: — Screen inbound numbers for fraud before connecting
— CNAM caller ID enrichment
bulk-number-validation-cleaner-python — Validate and clean lists of numbers
SOCIAL SHARE CARD GENERATOR