---
name: phone-osint
description: Build and use reverse phone lookup tools — multi-source aggregators, web scrapers, and reverse-engineered caller ID APIs.
---

# Phone OSINT

Reverse phone lookup via aggregation of multiple free web scrapers and reverse-engineered mobile APIs. Save results in SQLite cache, serve via CLI or HTTP.

## Architecture

```
phonelookup.py          # CLI + HTTP server + parallel aggregator
cache.py                # SQLite cache (7-day TTL)
config.json             # API tokens
providers/
├── carrier.py          # phonenumbers lib (offline, always works)
├── spamcheck.py        # spamcalls.net + tellows.de scraping
├── anywho.py           # Anywho.com scraper (no CF)
├── zlookup.py          # Zlookup.com scraper (no CF)
├── truecaller.py       # OTP-login API via truecallerpy
└── syncme.py           # Guest-mode API
```

## Sources That Work

| Source | Method | Anti-bot | Data |
|--------|--------|----------|------|
| phonenumbers lib | offline (Python pkg) | none | Country, region, carrier, timezone, line type |
| spamcalls.net | HTTP scrape | None | Spam score, report count |
| tellows.de | HTTP scrape | None | Spam score |
| zlookup.com | HTTP POST | None | Name, carrier, location |
| anywho.com | HTTP GET + cookies | Rate-limit (403 on burst) | Name, location, age, aliases |
| Truecaller API | Reverse-engineered (OTP) | Rotation | Name, photo, email, address, spam |
| Sync.ME API | Reverse-engineered (guest) | API key | Name, photo, location |

## Sources to Avoid

| Site | Blocked By | Notes |
|------|-----------|-------|
| whitepages.com | Cloudflare + Turnstile + DataDome | Extreme — needs solver + proxies |
| truepeoplesearch.com | Cloudflare + Turnstile (all routes) | Very hard, same operator as FPS |
| fastpeoplesearch.com | Cloudflare + Turnstile (all routes) | Very hard |
| peekyou.com | CF proxy + SPA (JS-rendered) | Static HTTP won't work |
| spytox.com | Offline | DO server, non-CF but down |
| beenverified.com | CF + paywall | Paid sub required |

## Truecaller API

Guest registration is **dead** (all install endpoints return 404 since mid-2025). Current flow requires OTP:

```
1. POST https://account-asia-south1.truecaller.com/v2/sendOnboardingOtp
   Header: clientsecret: lvc22mp3l1sfv6ujg83rd17btt
   Body: {phoneNumber, dialingCode, countryCode, installationDetails}
   → Returns requestId

2. POST https://account-asia-south1.truecaller.com/v1/verifyOnboardingOtp
   Body: {phoneNumber, requestId, otp}
   → Returns installationId (= bearer token)

3. GET https://search5-noneu.truecaller.com/v2/search?q=<digits>&countryCode=US&type=4
   Header: Authorization: Bearer <installationId>
```

Login wrapper: `truecallerpy` Python library (`pip install truecallerpy`).
Token lifetime: ~24-48h.

## OpenCode Server Key Storage

**Two approaches work — pick one, don't mix:**

1. **Inline in `opencode.json` or `opencode.server.json`** — put `apiKey` directly under `provider.<name>.options`. Works fine. The `***` you see after editing is deliberate: the key is replaced with `***` in the serialized config for security, but the actual key is still stored in the process. If you see truncated keys like `sk-or-...e437` with literal `...` characters in the middle, the key was physically truncated during entry — those are NOT a security mask, they're broken keys.

2. **`auth.json`** — store in `~/.local/share/opencode/auth.json`:
```json
{"providers": {"openrouter": {"apiKey": "sk-or-v1-..."}}}
```
Use `opencode providers login` interactively, or write auth.json directly + restart.

**Key verification:** `curl -su user:pass http://localhost:4096/api/providers 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); [print(k, bool(v.get('options',{}).get('apiKey',''))) for k,v in d.items()]"`

## Project Status & Migration

The original `opencode-ai/opencode` repo was **archived Sep 2025**. The project continues at **`charmbracelet/crush`** (25k+ stars, actively maintained). Crush is v0.77.0+ vs opencode's final v1.17.4.

**Key differences:**
- Crush has **no web UI** (`crush server` is headless only, no `crush web`)
- Crush has a **different config format** (`crush.json`, not `opencode.json`)
- Crush CLI: `crush` / `crush run` / `crush server` (same patterns, different binary)
- Install: download release tarball from GitHub releases

## Web UI Settings Crash

OpenCode v1.17.3 embedded web assets crash on settings page:

```
Error: Settings context must be used within a context provider
  at use (index-D7UlFC42.js:2:41525)
  at Sr (home-pp5zBMl2.js:2:11001)
```

**Root cause:** React `SettingsContext.Provider` missing from settings dialog component tree. The General tab is active by default and triggers the crash. Providers/Models tabs also crash because the initial error poisons the render tree.

**Workarounds (all three work):**
1. Use **OpenCode Desktop** on the client machine — reads local config, not web UI
2. Use **direct CLI** — `opencode -m <model>` on the server
3. **Crush replacement** — install `charmbracelet/crush` and use `crush` CLI instead

## Aggregation Pattern

```python
from concurrent.futures import ThreadPoolExecutor, as_completed

PROVIDERS = [("carrier", carrier), ("spamcheck", spamcheck), ...]

def lookup(phone):
    result = {"phone": phone, "carrier": {}, "spam": {}, "identity": {}}
    with ThreadPoolExecutor(max_workers=6) as pool:
        futures = {pool.submit(run, n, m): n for n, m in PROVIDERS}
        for fut in as_completed(futures):
            name, data = fut.result()
            # Merge by category (carrier, spam, identity)
    cache.set(phone, result)
    return result
```

## Pitfalls

- Truecaller's `search5-noneu.truecaller.com` works on HTTP/2 only — use `httpx[http2]`
- Hardcoded tokens from GitHub repos are always burned (suspended) — never reuse them
- `sk-or-...e437` or similar in config files means the key was truncated during entry — `...` in the middle is literal, not a mask. Real keys are 35-73 chars
- KWrite/Kate buffers that aren't saved to disk can't be read via SSH — use `xsel -b` for clipboard, or `qdbus` for open document content (if qdbus is installed)
- **OpenCode web UI settings crash** — v1.17.3 embedded web assets have a React bug: `Settings context must be used within a context provider` at `home-pp5zBMl2.js:2:11001`. The `SettingsContext.Provider` doesn't wrap the settings dialog. No fix exists (repo archived). Providers/Models tabs crash after initial load. Workaround: use Desktop app or CLI instead. See `references/crush-migration.md`.
- **OpenRouter free models need extra headers** — Models with `:free` suffix (e.g. `nex-agi/nex-n2-pro:free`) require `HTTP-Referer` and `X-Title` headers in the API request. Without them, OpenRouter returns 401/403. Add to OpenCode provider config: `"headers": {"HTTP-Referer": "http://localhost:4096", "X-Title": "OpenCode Server"}` under `provider.<name>.options`.
- **OpenCode npm version vs embedded web UI mismatch** — npm package `opencode-ai@1.17.4` has v1.17.3 web UI assets embedded. The web assets weren't rebuilt for 1.17.4. The CLI version says 1.17.4 but the browser shows `Version: 1.17.3` on the error page.

## User Style

This user wants **direct action, not options.** When they ask for a lookup, just do it. Don't explain architectures, don't offer setup alternatives, don't describe what you're about to do. Run the tool and deliver results. If a setup step requires user input (OTP), present it as a single ask — not a menu of choices.
