Features Docs Pricing FAQ Get API Key →
API Reference · v1

Turn an email into a full crypto OSINT profile — identity, socials, and every wallet tied to it with live balances and exchange history.

All systems operational Base URL: https://pkill.lol

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.

HTTP Header
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.

GET /health
Returns a small JSON payload confirming the API process is alive, along with the current in-flight and pending request counts. Safe to call as often as you like — this endpoint never touches upstream providers or caches.

Headers

None required. This endpoint is public and does not consume any of your key's time.

Request

cURL
curl -X GET https://pkill.lol/health

Response · 200 OK

JSON
{
  "success": true,
  "status": "ok",
  "in_flight": 0,
  "pending": 0
}

Response fields

success boolean
Always true on a 200 response.
status string
Always the literal string "ok" when the API process is up and serving traffic.
in_flight integer
Number of emails currently being resolved by the singleflight engine. Non-zero only during active lookups; each unique email is counted once regardless of how many concurrent callers are waiting on it.
pending integer
Number of request handlers currently inside /v1/lookup — cache hits, in-flight joins, and full upstream runs all count. Admission control rejects new requests once this reaches 500 with 503 server_busy.
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.

GET /v1/key/status
Returns the current state of the API key used in the request. This endpoint does not consume any of your remaining time and is safe to call as often as you like.

Headers

HeaderDescription
X-API-Key Required Your pk_live_… key.

Request

cURL
curl -X GET https://pkill.lol/v1/key/status \
  -H "X-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxxxxxx"

Response · 200 OK

JSON
{
  "success": true,
  "latency": "<1ms",
  "active": true,
  "time_remaining": "4 days, 13 hours, 22 minutes"
}

Response fields

success boolean
Always true on a 200 response.
latency string
Server-side processing time, formatted like "<1ms", "42ms", or "1.20s".
active boolean
Always true when this endpoint returns 200. Expired keys never reach this endpoint — they're rejected up-front with a 402.
time_remaining string
Human-readable remaining time, up to the three largest units. Examples: "3 months, 20 days, 6 hours", "4 days, 13 hours, 22 minutes", "1 hour, 5 minutes, 30 seconds". The literal string "unlimited" is returned for keys with no expiry timestamp.
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.

POST /v1/lookup
Runs a full intelligence pass on the target email. Resolves identity, socials, and every linked crypto wallet with live balances.

Headers

HeaderDescription
X-API-Key RequiredYour pk_live_… key.
Content-Type RequiredMust be application/json.

Body parameters

FieldTypeDescription
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
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

JSON
{
  "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

success boolean
Always true on a 200 response.
latency string
Server-side processing time. Formatted like "<1ms", "842ms", or "2.14s".
email string
Echo of the queried email, lowercased.
name string | null
Resolved full name, or null if not found.
carrier string | null
Carrier name and phone number, formatted like "AT&T: +1 (408) 618-9689". If only the phone was found, just the phone is returned.
address string | null
Home address in the format our sources return — usually STREET CITY STATE ZIP. Omitted entirely when include_pii is false.
ssn string | null
SSN in XXX-XX-XXXX format if the source contains it. Omitted entirely when include_pii is false.
dob string | null
Date of birth in YYYY-MM-DD format. Omitted entirely when include_pii is false.
home_value string | null
Formatted string. Either as a dollar amount (e.g. "$661,980"), or a fallback wealth score (e.g. "84 - Wealth Score"), or null if neither source produced data.
socials string[]
Array of full URLs to linked social profiles. Empty array when nothing is found.
wallets object[]
Array of wallet objects. Empty array when no wallets were found. See the wallet schema below.

Wallet object

chain string
One of bitcoin, ethereum, solana, or zcash.
address string
The on-chain address. Format depends on chain — base58 for BTC/SOL, 0x-prefixed hex for ETH.
usd number
Live USD value of the wallet's native balance, rounded to 2 decimals. 0.00 for empty wallets.
nft_usd number
Only present on Ethereum wallets. Estimated USD value of the address's NFT portfolio, rounded to 2 decimals. Omitted when there are no NFTs or when the value is zero.
exchanges string[]
Only present when the address has interacted with known exchanges. Array of exchange names, sorted alphabetically. Omitted when empty.

Error Codes

Every error returned by the application is JSON with the same shape, so you can handle failures uniformly:

Error Response
{
  "success": false,
  "error": {
    "code": "invalid_api_key",
    "message": "The API key is invalid or has been revoked."
  }
}
StatusCodeMeaning
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

Shell
# 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

Python 3
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

Node 18+
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)}`)
  );
})();