The blocklist behind 2,132 firewalled addresses, plus the client fingerprints behind them, available as a feed. Every call carries your X-API-Key and a fresh proof of work, so a stolen key is worth far less than a stolen password.
Authentication
Send your key as the X-API-Key header on every request. Keys are issued per account with a monthly quota — create one in the developer console.
Open developer console →Start here — public summary
The network summary needs no key at all. It is the quickest way to confirm you can reach the API.
curl -s https://ghostnet.thedihmaster.lol/api/summary
The proof-of-work gate
Every feed request is a two-step exchange. It costs a fraction of a second of real CPU work and it cannot be replayed — each challenge is single-use and burned the instant it is spent.
GET /api/pow-challenge with your key returns an identifier, a random challenge_token and a difficulty.
Find any nonce where sha256(identifier + challenge_token + nonce) begins with difficulty zero hex characters.
Send the token and nonce as X-PoW-Challenge and X-PoW-Nonce on the feed request, within 300 seconds.
Challenge response
{
"identifier": "[email protected]",
"challenge_token": "a1b2c3d4e5f6...",
"difficulty": 4,
"algorithm": "sha256",
"expires_in_seconds": 300
}
curl -s -H "X-API-Key: $KEY" \
https://ghostnet.thedihmaster.lol/api/pow-challenge
Endpoints
Public totals, severity mix and top source countries.
No key requiredIssue a single-use proof-of-work challenge, valid 300s.
Key requiredThe live feed of firewalled source addresses, masked to /24.
Key + PoWClient signatures behind the IPs. ?family= ja3, ja4, http, ssh, rdp, generic.
Full example — cURL
Fetches a challenge, solves it in the shell, and spends it on the blocklist. Needs jq and sha256sum.
#!/usr/bin/env bash
set -euo pipefail
KEY="gk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # from your developer console
BASE="https://ghostnet.thedihmaster.lol"
# 1 - fetch a single-use challenge (needs jq)
CH=$(curl -s -H "X-API-Key: $KEY" "$BASE/api/pow-challenge")
ID=$(jq -r .identifier <<<"$CH")
TOKEN=$(jq -r .challenge_token <<<"$CH")
DIFF=$(jq -r .difficulty <<<"$CH")
PREFIX=$(printf '0%.0s' $(seq 1 "$DIFF"))
# 2 - solve: sha256(identifier + challenge_token + nonce) must start with $PREFIX
n=0
while :; do
H=$(printf '%s%s%s' "$ID" "$TOKEN" "$n" | sha256sum)
[ "${H:0:$DIFF}" = "$PREFIX" ] && break
n=$((n+1))
done
# 3 - spend the solved challenge on the feed
curl -s -H "X-API-Key: $KEY" \
-H "X-PoW-Challenge: $TOKEN" \
-H "X-PoW-Nonce: $n" \
"$BASE/api/threats/blocklist" | jq .
Full example — Python
A reusable call() that handles the whole challenge → solve → spend cycle. Needs requests.
#!/usr/bin/env python3
"""GhostNet Threat Intelligence API - reference client."""
import itertools
import hashlib
import requests
API_KEY = "gk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # from your developer console
BASE = "https://ghostnet.thedihmaster.lol"
def solve(identifier, token, difficulty):
"""Find a nonce whose sha256(identifier+token+nonce) starts with N zeros."""
target = "0" * difficulty
for n in itertools.count():
nonce = str(n)
digest = hashlib.sha256(f"{identifier}{token}{nonce}".encode()).hexdigest()
if digest.startswith(target):
return nonce
def call(path, params=None):
# 1 - every request needs its own fresh, single-use challenge
ch = requests.get(f"{BASE}/api/pow-challenge",
headers={"X-API-Key": API_KEY}, timeout=10).json()
# 2 - solve it locally (difficulty 4 is a few hundred ms)
nonce = solve(ch["identifier"], ch["challenge_token"], ch["difficulty"])
# 3 - spend the solved challenge on the feed
resp = requests.get(f"{BASE}{path}", params=params, timeout=10, headers={
"X-API-Key": API_KEY,
"X-PoW-Challenge": ch["challenge_token"],
"X-PoW-Nonce": nonce,
})
resp.raise_for_status()
return resp.json()
if __name__ == "__main__":
blocklist = call("/api/threats/blocklist")
print(f"blocklist entries: {len(blocklist)}")
ja3 = call("/api/threats/fingerprints", {"family": "ja3"})
print(f"ja3 signatures: {ja3['count']}")
Polling efficiently
The feed is large and changes gradually, so two optional, fully backward-compatible mechanisms let a steady poller pull only what it needs.
Keep the ETag from your last response and send it back as If-None-Match. If nothing changed you get 304 Not Modified — no body, no proof of work, and no quota spent.
Pass ?since= an ISO 8601 timestamp or epoch seconds to receive only records newer than your cursor, instead of the whole feed.
# only download the feed if it changed (304 = unchanged, costs no quota)
curl -s -H "X-API-Key: $KEY" -H "If-None-Match: $ETAG" \
https://ghostnet.thedihmaster.lol/api/threats/blocklist
# or pull only what is new since a timestamp (ISO 8601 or epoch seconds)
curl -s -H "X-API-Key: $KEY" \
"https://ghostnet.thedihmaster.lol/api/threats/blocklist?since=2026-08-20T00:00:00"