Turn an email into a full crypto OSINT profile — identity, socials, and every wallet tied to it with live balances and exchange history.
Overview
The pkill Lookup API gives you programmatic access to our breach intelligence engine. You send an email, we return a structured JSON profile with:
- Full name, carrier + phone, home address, DoB, SSN, and a home value estimate
- Every linked social profile (GitHub, X, Farcaster, Reddit, Lens, and more)
- Every crypto wallet associated with that identity — Bitcoin, Ethereum, Solana, Zcash
- Live USD balance per wallet, plus NFT portfolio value on Ethereum addresses
- Exchange interaction history for each wallet (Binance, Coinbase, Kraken, etc.)
The API is time-based. Your key stays valid for the duration you purchased (1 day, 7 days, or 1 month) and can be topped up at any time. There are no request rate limits, no proxies required, and you will never encounter a captcha.
Authentication
Every authenticated request must include your API key in the X-API-Key header. Keys are prefixed with pk_live_ and issued through our Telegram bot.
X-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Your key is bound to the Telegram account that purchased it. To check remaining time, use the status endpoint. To rotate your key, click the Reset API Key button inside @pkill_bot — remaining time carries over automatically.
Key Security
Never share your API key
Treat your key like a password. Anyone who has it can run lookups on your remaining time. Never post it on forums, paste it in Discord, or send it to anyone who asks.
Never commit it to Git
Do not hardcode your key in source files that get pushed to GitHub, GitLab, or any public repo. Attackers scrape new commits in seconds. Use environment variables instead:
PKILL_API_KEY=your_key_here
Best practices
· Load keys from env vars or a secrets manager, never from source code.
· Add .env to your .gitignore before your first commit.
· Rotate your key by clicking the Reset API Key button in the bot if you suspect a leak.
· Use separate keys for production and testing where possible.
· Serve lookups from your backend, never from client-side JavaScript — a browser-side key is public the moment it ships.
Health
Public health check. No authentication required. Point your uptime monitor, load balancer, or status page at this endpoint.
Headers
None required. This endpoint is public and does not consume any of your key's time.
Request
curl -X GET https://pkill.lol/health
Response · 200 OK
{ "success": true, "status": "ok", "in_flight": 0, "pending": 0 }
Response fields
Interpreting the counters
Both in_flight and pending are point-in-time snapshots — they change with every request. Under normal load you'll see both near 0. A sustained non-zero pending approaching 500 means the API is saturated and you should back off. This endpoint does not read the config or keys files' contents; it only refreshes their on-disk mtime checks.
Key Status
Check how much time is left on your key before spending it on lookups. Useful for health checks, dashboards, or to warn users of your own app when access is about to expire.
Headers
| Header | Description |
|---|---|
| X-API-Key Required | Your pk_live_… key. |
Request
curl -X GET https://pkill.lol/v1/key/status \ -H "X-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Response · 200 OK
{ "success": true, "latency": "<1ms", "active": true, "time_remaining": "4 days, 13 hours, 22 minutes" }
Response fields
Expired keys return 402, not 200 with active:false
If your key has run out of time, this endpoint responds with 402 key_expired and the standard error body. Your client should treat any non-200 response as "key cannot be used" — there's no need to parse a boolean out of the body.
Lookup
The main endpoint. Send an email address and get the full OSINT + crypto profile back in one response. This is the endpoint that consumes your key's time — but there's no per-request cost, no rate limit, and no concurrency cap that you'll realistically hit.
Headers
| Header | Description |
|---|---|
| X-API-Key Required | Your pk_live_… key. |
| Content-Type Required | Must be application/json. |
Body parameters
| Field | Type | Description |
|---|---|---|
| email Required | string | The email address to look up. Must be a valid RFC 5322 email. Case-insensitive — the response always echoes it in lowercase. |
| include_pii Optional | boolean | Defaults to true. Set to false to omit ssn, dob, and address from the response. Useful if your upstream compliance policy forbids storing raw PII. |
Request
curl -X POST https://pkill.lol/v1/lookup \ -H "X-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"email":"lijoe408@gmail.com"}'
Response · 200 OK
{ "success": true, "latency": "2.14s", "email": "lijoe408@gmail.com", "name": "Zhe Li", "carrier": "AT&T: +1 (408) 618-9689", "address": "3475 CORTESE SJ CA 95127", "ssn": "439-79-7926", "dob": "1985-11-30", "home_value": "$661,980", "socials": [ "https://github.com/zheli", "https://x.com/linuxcity", "https://farcaster.xyz/zhe", "https://reddit.com/user/zheli", "https://lens.xyz/zheli.lens" ], "wallets": [ { "chain": "bitcoin", "address": "36znttBacw1VobTowwRZtfw6LM9fTvJ1aT", "usd": 0.00 }, { "chain": "ethereum", "address": "0x94ddd3dfc7f2b09980daf722c1563e250c11b988", "usd": 1981.05, "nft_usd": 1507.92, "exchanges": ["Binance", "Bittrex", "Celsius Network", "Coinbase", "Crypto.com", "Gate", "HTX", "HitBTC", "Kraken", "Nexo", "Poloniex"] }, { "chain": "ethereum", "address": "0x15362203480787493579c40a387c8d7dc7f3767b", "usd": 84.58, "nft_usd": 79.84 }, { "chain": "solana", "address": "7MPB8H6zeDvp3HGxbhKcmuN3xNQdHC4BnwE2AkMdKwmq", "usd": 0.00 } ] }
Response Schema
Fields marked null in a live response simply weren't found for that target — you'll always get the same shape back, so your parsing logic doesn't have to branch on presence.
Top-level fields
Wallet object
Error Codes
Every error returned by the application is JSON with the same shape, so you can handle failures uniformly:
{ "success": false, "error": { "code": "invalid_api_key", "message": "The API key is invalid or has been revoked." } }
| Status | Code | Meaning |
|---|---|---|
| 200 | — | Success. Body contains the full profile or status payload. |
| 400 | invalid_request | Body isn't valid JSON, the Content-Type header is missing, or a field failed validation (for example, an invalid email address). |
| 401 | missing_api_key | No X-API-Key header was sent. |
| 401 | invalid_api_key | The key doesn't exist, has been rotated with the Reset API Key button, or was never issued by the bot. |
| 402 | key_expired | The key is valid but the time on it has run out. Top up via @pkill_bot to reactivate. |
| 422 | no_results | The lookup ran but nothing was found for the target. Still a valid call — just an empty profile. Responses are negatively cached for 60 seconds by default. |
| 503 | upstream_unavailable | One of our upstream data sources is having issues. Retry with exponential backoff. |
| 503 | server_busy | Admission control — too many in-flight requests. Wait a moment and retry. |
| 504 | upstream_timeout | The lookup exceeded its 90-second time budget. Retry. |
Retry strategy
Only retry on 503 and 504. Use exponential backoff starting at 1s, doubling each attempt, with a cap of ~30s. Never retry on 4xx client errors — they won't succeed on the second try.
Unknown paths (e.g. /v1/nonexistent) and unsupported methods return Starlette's default error shape ({ "detail": "Not Found" }), not the shape above.
Rate Limits
There are none
No rate limits. No proxies needed. No captchas, ever. Run as many lookups as you want for the entire duration of your plan. The only thing that ends your access is time, and you can top that up at will.
If you're building a high-throughput integration and want to schedule a heads-up so we can make sure your traffic is smooth, message us in @pkill_bot. We're happy to coordinate.
Examples
cURL
# Check remaining time curl -X GET https://pkill.lol/v1/key/status \ -H "X-API-Key: $PKILL_API_KEY" # Run a lookup curl -X POST https://pkill.lol/v1/lookup \ -H "X-API-Key: $PKILL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email":"lijoe408@gmail.com"}'
Python
import os import requests API_KEY = os.environ["PKILL_API_KEY"] # never hardcode this BASE = "https://pkill.lol" def time_left(): r = requests.get( f"{BASE}/v1/key/status", headers={"X-API-Key": API_KEY}, timeout=10, ) r.raise_for_status() return r.json()["time_remaining"] def lookup(email): r = requests.post( f"{BASE}/v1/lookup", headers={"X-API-Key": API_KEY}, json={"email": email}, timeout=90, ) r.raise_for_status() return r.json() print(f"Time remaining: {time_left()}") profile = lookup("lijoe408@gmail.com") for w in profile["wallets"]: print(w["chain"], w["address"], f"${w['usd']:,.2f}")
Node.js
const API_KEY = process.env.PKILL_API_KEY; // never hardcode this const BASE = "https://pkill.lol"; async function timeLeft() { const r = await fetch(`${BASE}/v1/key/status`, { headers: { "X-API-Key": API_KEY }, }); const data = await r.json(); return data.time_remaining; } async function lookup(email) { const r = await fetch(`${BASE}/v1/lookup`, { method: "POST", headers: { "X-API-Key": API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ email }), }); return r.json(); } (async () => { console.log("Time remaining:", await timeLeft()); const p = await lookup("lijoe408@gmail.com"); p.wallets.forEach(w => console.log(w.chain, w.address, `$${w.usd.toFixed(2)}`) ); })();