Developer Guide

Python integration guide

A minimal Telebase client in Python is a single function: build the request URL with the phone number URL-encoded, send it with a Bearer token, and parse the JSON response. This page covers that function, the three error responses worth handling explicitly, and a simple retry pattern for transient network failures.

A minimal client

Use the standard library's urllib.parse.quote to URL-encode the phone number, and requests for the HTTP call. The + in an E.164 number must become %2B; passing a raw + in a query string is interpreted as a space and will produce a failed or incorrect lookup.

Python
import requests
from urllib.parse import quote

API_KEY = "tb_live_xxxxxxxxxxxxxxxxxxxxxxxx"
BASE_URL = "https://telebase.fatcatremote.com/api/lookup"

def lookup(phone_number: str) -> dict:
    encoded_number = quote(phone_number, safe="")
    response = requests.get(
        f"{BASE_URL}?phone={encoded_number}",
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=10,
    )
    response.raise_for_status()
    return response.json()

result = lookup("+447700900000")
print(result["carrier"], result["numberType"], result["simSwap"])

quote(phone_number, safe="") handles the + to %2B conversion correctly, so there is no need to encode it manually.

Handling errors explicitly

response.raise_for_status() will raise an HTTPError for any non-200 response, but it is worth catching that and branching on the status code so your onboarding or authentication flow can respond appropriately rather than surfacing a raw exception.

Python
def lookup(phone_number: str) -> dict:
    encoded_number = quote(phone_number, safe="")
    response = requests.get(
        f"{BASE_URL}?phone={encoded_number}",
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=10,
    )
    if response.status_code == 401:
        raise RuntimeError("Missing or malformed API key")
    if response.status_code == 400:
        raise ValueError(f"Invalid phone number: {phone_number}")
    if response.status_code == 402:
        raise RuntimeError("Account balance is at zero, top up via the dashboard")
    response.raise_for_status()  # catches anything else non-200
    return response.json()

What each status means

A retry pattern for transient failures

401, 400 and 402 will not resolve themselves on retry, since each reflects something about the request or account that needs fixing first. Retries are worth applying only to genuinely transient failures: network timeouts, connection errors, and 5xx responses from the server. A short exponential backoff with a small number of attempts is enough for most integrations.

Python
import time

def lookup_with_retry(phone_number: str, max_attempts: int = 3) -> dict:
    for attempt in range(max_attempts):
        try:
            return lookup(phone_number)
        except (requests.ConnectionError, requests.Timeout):
            if attempt == max_attempts - 1:
                raise
            time.sleep(2 ** attempt)  # 1s, 2s, 4s
        except requests.HTTPError as exc:
            if exc.response.status_code >= 500 and attempt < max_attempts - 1:
                time.sleep(2 ** attempt)
                continue
            raise

Do not retry on 401, 400 or 402: fix the API key, the phone number format, or the account balance instead.

Signals available today

Live in every response

SIM swap detection is launching. Early access is open now.

The simSwap field is present in every response today and returns UNKNOWN in GB, DE, NL and FR while carrier registration completes. The Python client above needs no changes when it goes live for your market. See the SIM swap detection API.

New to the API entirely? Start with the quickstart guide for the first request end to end.

$0.03 per query. No contract. No minimum spend. Billed via Paddle.
Request early access