# Phone Lookup Source Research (June 2026)

## Source Availability Matrix

| Site | Cloudflare | Turnstile | DataDome | Scrape Difficulty | Status |
|------|------------|-----------|----------|-------------------|--------|
| whitepages.com | ✅ | ✅ (results) | ✅ v5.7.0 | **Extreme** — needs solver + proxies + fingerprint | Avoid |
| truepeoplesearch.com | ✅ | ✅ (all routes) | ❌ | **Very High** — CF + Turnstile on every page | Avoid |
| fastpeoplesearch.com | ✅ | ✅ (all routes) | ❌ | **Very High** — same operator as TPS | Avoid |
| **anywho.com** | ❌ | ❌ | ❌ | **Easiest** — Next.js, session cookies | ✅ Use |
| **zlookup.com** | ❌ | ❌ | ❌ | **Easy** — nginx/1.14.0, no CF | ✅ Use |
| peekyou.com | ✅ (proxy only) | ❌ | ❌ | Moderate — SPA, client-side routing | ⚠️ Hard |
| spytox.com | ❌ | ❌ | ❌ | **Was easiest** — DO server, offline now | ❌ Offline |
| spamcalls.net | ❌ | ❌ | ❌ | **Easy** — simple HTML scraping | ✅ Use |
| tellows.de | ❌ | ❌ | ❌ | **Easy** — simple HTML scraping | ✅ Use |
| beenverified.com | ✅ | ❌ (200) | ❌ | Paid sub behind login | ❌ Paywall |
| zabasearch.com | ✅ | ✅ (403) | ❌ | Empty CF challenge | ❌ Blocked |
| radaris.com | ✅ | ✅ (403) | ❌ | Empty CF challenge | ❌ Blocked |
| phonesearch.com | ❌ | ❌ | ❌ | nginx/1.19.5 cheap VPS | ⚠️ Untested |

## Truecaller API (Best Data Source)

**Status (June 2026):** Guest registration is **dead**. All old install endpoints return 404:
- `api4.truecaller.com/v1/sessions/install` → 404
- `search5-noneu.truecaller.com/v1/sessions/install` → 404
- `app-tr.truecaller.com/v1/sessions/install` → timeout

**Current working flow requires OTP:**

```
Step 1: POST https://account-asia-south1.truecaller.com/v2/sendOnboardingOtp
  Headers: clientsecret: lvc22mp3l1sfv6ujg83rd17btt
           user-agent: Truecaller/11.75.5 (Android;10)
  Body: {countryCode, dialingCode, installationDetails, phoneNumber, region, sequenceNo}
  Response: {requestId: "..."}

Step 2: POST https://account-asia-south1.truecaller.com/v1/verifyOnboardingOtp
  Body: {phoneNumber, requestId, otp: "XXXXX"}
  Response: {installationId: "..."}  ← This is the Bearer token

Step 3: GET https://search5-noneu.truecaller.com/v2/search?q={digits}&countryCode=US&type=4
  Authorization: Bearer {installationId}
  Response: [{name, phones, addresses, internetAddresses, spamInfo, image, ...}]
```

**Python library:** `truecallerpy` — provides `login(phone_number)`, `verify_otp(phone, json_data, otp)`, `search_phonenumber(phoneNumber, countryCode, installationId)`.

**Pitfall:** `truecallerpy` uses HTTP/2 via httpx internally. Install with `pip install httpx[http2]` or `pip install truecallerpy` will pull it, but if you get `ImportError: Using http2=True, but the 'h2' package is not installed`, run `pip install httpx[http2]`.

**CLI integration pattern** — expose a `--source-login` flag in the aggregator tool that interactively walks the user through OTP:

```python
def _login_handler(phone: str):
    from truecallerpy import login, verify_otp
    import asyncio, json
    
    result = asyncio.run(login(phone))
    # result.data.requestId is the OTP request handle
    otp = input("Enter OTP: ")
    verify = asyncio.run(verify_otp(phone, result["data"], otp))
    token = verify.get("installationId")
    # Save to config.json
```

**Known burned tokens:**
- `a2i0t--ncJeSVF-VYZg3zGmFzQGZh-rY_c2B16r31IFj9Ie9ql6LVzF8p7KronlL` → "Account suspended" (401)
- All hardcoded `installToken` values from decompiled APKs are rotated

**Pitfall — 20003 "Verification failed" is misleading:** The OTP send endpoint returns HTTP 200 with `{"status": 20003, "message": "Verification failed", "requestId": "..."}` even when the SMS was sent successfully. The `requestId` is still valid for the verify step. This is NOT a failure — proceed with OTP entry. The message is a misnomer from Truecaller's backend; "verification failed" refers to a device pre-check, not the OTP send.

**400 Bad Request on repeated login attempts:** If the first OTP send succeeded (got requestId) but subsequent attempts return 400, the API is rate-limiting this IP. Wait ~10 seconds between retries.

**Token lifetime:** ~24-48 hours. Re-login required after expiry. Build a re-login reminder into the provider's `_note` when 401 is returned.

## Working Free/Scrape Sources

### phonenumbers library (offline)
- `pip install phonenumbers`
- Always works, zero API calls
- Returns: country, region, carrier, timezone, line type
- No setup needed

### spamcalls.net
- `GET https://spamcalls.net/en/number/{e164_digits}`
- No Cloudflare, simple HTML, regex scrape
- Returns: report count

### tellows.de
- `GET https://www.tellows.de/number/{e164_digits}`
- No Cloudflare, simple HTML
- Returns: tellows-score (0-100)

### anywho.com
- No Cloudflare, Next.js server-side rendered
- URL: `https://www.anywho.com/phone/{formatted}`
- Formatted as `(XXX) XXX-XXXX` for US
- Requires session cookie from homepage visit first
- Rate limit: ~1 req/5s
- Returns: name, location, age_range, aliases

### zlookup.com
- No Cloudflare, nginx PHP backend
- URL: `POST https://www.zlookup.com/lookup` with form data
- Returns: name, location, carrier
- Simple form-based scraping

## Sync.ME API

- `POST https://api.sync.me/api/caller_id/caller_id/v2`
- RSA+AES encrypted request body
- Hardcoded ACCESS_TOKEN in decompiled APK: `FDGAVu...JyBY`
- Encrypted with RSA public key extracted from APK
- Returns: name, picture, country, spam count, networks (social links)
- Harder to implement — needs AES + RSA + gzip

## Architecture Notes

- 6 providers in parallel via ThreadPoolExecutor(max_workers=6)
- Each provider runs independently — slow/scraped providers don't block fast offline ones
- SQLite cache prevents re-hitting scraped sites within 7-day window
- `_providers` dict stores raw per-provider results for debugging
- Merged output promotes key fields to top level: `carrier`, `spam`, `identity`
- Server binds `0.0.0.0` for LAN/Tailscale access
