Why not just parse WHOIS yourself
I tried that first. Raw WHOIS is unstructured, every registry formats it differently, and GDPR turned half the fields into REDACTED FOR PRIVACY. Some registries throttle port 43 after a few dozen queries. A few ccTLDs barely answer at all.
RDAP was supposed to fix this with clean JSON, and it mostly does — but coverage is still patchy and the schema wobbles between registries.
What I actually needed was boring and specific: an expiry date that parses the same way every time, across the 23 TLDs my projects and clients use. I also do light OSINT work, so SSL cert data, subdomain takeover checks, and some kind of threat signal were on the wishlist.
The setup: same 150 domains, same machine, 3 timed runs each, medians recorded. Mostly I cared about one thing — does the expiry field come back populated?
The 12, side by side
# API Free tier Median latency Expiry field Notable extra 1 Raw RDAP (rdap.org) Unlimited, no key 240ms Yes, when registry has RDAP None 2 python-whois (GitHub) Free library Varies wildly Empty on 6 of 23 TLDs None 3 WhoisXML API ~500 credits/mo 610ms Yes History, brand monitoring 4 DomainTools Iris Trial only 700ms Yes Everything, enterprise price 5 WhoisAPI.com ~100/mo 520ms Yes Bulk endpoints 6 JsonWHOIS ~100/mo 480ms Yes Social data 7 IP2WHOIS Free tier 550ms Yes Simple, cheap 8 APILayer whois ~100/mo 590ms Yes Shared key with other APILayer APIs 9 Whoxy Tiny free tier 640ms Yes Reverse WHOIS 10 WhoisJson Free tier 510ms Yes DNS records 11 SecurityTrails ~50/mo 430ms Partial DNS history, OSINT gold 12 Domain WHOIS API (RapidAPI) Free tier 390ms Yes SSL, takeover check, threat scoreLatency numbers are from my box in Frankfurt; yours will differ. For a nightly cron the gap between 390ms and 610ms means nothing , it only matters if you’re building something interactive.
Where things broke
Every landing page looks great. The failures only show up at 1 AM.
python-whois was the DIY route, and I wanted it to win. Free, no key, no vendor. It returned an empty expiry field for 6 of my 23 TLDs, including .ai and .dev, because the parser regexes didn’t match what those registries send back. Fixing that means maintaining your own regex fork, and I’ve done that before. I’m not doing it again.
Raw RDAP is genuinely good for .com and .net. The bootstrap list had no RDAP server for two ccTLDs in my set, though, which dumped me right back to parsing port 43 text. One registry also rate-limited me around 30 requests per minute with zero warning in the docs.
One mid-tier API (I’ll be polite and skip the name) returned a 2023 expiry date for a domain I’d renewed that January. Stale cache. A monitor built on that pages you about ghosts , or stays quiet while a real expiry creeps up, which is worse. I now cross-check every new provider against 5 freshly renewed domains before trusting it.
DomainTools is excellent, and priced accordingly. “Contact sales” is fine if you have a SOC. I don’t.
And a self-own: I burned one provider’s entire 100-request free tier in a single afternoon because I forgot to cache responses while debugging the test runner. Rookie mistake, happily admitted.
What ended up mattering
It came down to three things:
- Expiry field populated across weird TLDs. Half the field failed here.
- A free tier big enough to run a real monitor. 100 requests a month is a demo, not a tier.
- Extras that kill other tools. I was running separate scripts for SSL expiry and takeover checks. One API bundled all of it under a single key: the Domain WHOIS API on RapidAPI. WHOIS lookup, SSL cert info, subdomain takeover detection, threat score.
The takeover check earned its keep during the audit that started all this. It flagged an old blog subdomain CNAME’d to a Heroku app nobody had owned in two years:
{
"domain": "old-blog.example.com",
"cname": "old-blog.herokuapp.com",
"provider": "Heroku",
"vulnerable": true,
"detail": "CNAME resolves but no app claims the hostname"
}
Enter fullscreen mode Exit fullscreen mode
The slug was unclaimed. Grab it, and you’re serving content on a subdomain we controlled. Classic dangling DNS. The threat score held up too: it rated a lookalike domain I’d registered for phishing-simulation practice at 87/100, while my real domains sat around 10-15. Directionally right, which is all I ask from a heuristic.
There’s also a GitHub repo if you want to read the code or file an issue.
How to use it
- Create a free RapidAPI account.
- Open the Domain WHOIS API listing and subscribe to the free tier.
- Copy your
x-rapidapi-keyfrom the dashboard. - Run it.
WHOIS lookup with curl:
curl -s "https://domain-whois-api2.p.rapidapi.com/whois?domain=example.com" \
-H "x-rapidapi-host: domain-whois-api2.p.rapidapi.com" \
-H "x-rapidapi-key: YOUR_KEY" | jq .
Enter fullscreen mode Exit fullscreen mode
Subdomain takeover check:
curl -s "https://domain-whois-api2.p.rapidapi.com/subdomain-takeover?domain=example.com" \
-H "x-rapidapi-host: domain-whois-api2.p.rapidapi.com" \
-H "x-rapidapi-key: YOUR_KEY" | jq .
Enter fullscreen mode Exit fullscreen mode
Python, hitting all four endpoints in one pass:
import requests
HOST = "domain-whois-api2.p.rapidapi.com"
HEADERS = {
"x-rapidapi-host": HOST,
"x-rapidapi-key": "YOUR_KEY",
}
def full_check(domain):
report = {}
for endpoint in ["whois", "ssl", "subdomain-takeover", "threat-score"]:
r = requests.get(
f"https://{HOST}/{endpoint}",
headers=HEADERS,
params={"domain": domain},
timeout=10,
)
r.raise_for_status()
report[endpoint] = r.json()
return report
if __name__ == "__main__":
data = full_check("example.com")
print("expires:", data["whois"].get("expiration_date"))
print("ssl:", data["ssl"])
print("takeover:", data["subdomain-takeover"])
print("threat:", data["threat-score"])
Enter fullscreen mode Exit fullscreen mode
Swap in your domains, wrap it in cron, alert at 30, 14, and 7 days.
My current stack
Nightly cron against the RapidAPI free tier for the WHOIS, SSL, and takeover sweep, with raw RDAP as a fallback when a TLD misbehaves. Alerts go to a webhook that pings my phone. It’s cost me $0 so far, and I haven’t lost a domain since.
The squatter still has my old domain, by the way. The listing dropped from $4,800 to $2,200 last I checked. I’m not paying, out of pure spite, which is petty and feels great.
If you’re comparing providers yourself, also look at WhoisXML API for bulk historical data and SecurityTrails when DNS history matters more than WHOIS. Both held up fine in my tests.
Which TLD has broken your WHOIS parsing the hardest? Drop it in the comments , I’ll add it to the test set and rerun the numbers.
How do you currently handle VPN detection in your stack?
답글 남기기