WHOIS is on its way out. The registries now publish the same data through RDAP, a JSON API that's free, needs no key, and returns registrar, creation and expiry dates, and status in a consistent format. If you track client domains, buy expiring names, or audit a portfolio, a few lines of Python replace a paid WHOIS API.
The script
rdap.org is a public bootstrap service: request rdap.org/domain/<name> and it redirects you to the right registry. Standard library only:
"""Bulk domain lookup with RDAP (the official successor to WHOIS): registrar, created, expires, status.
Usage: python3 domain_expiry.py apify.com python.org some-free-name-123.com"""
import json
import socket
import sys
import urllib.error
import urllib.request
from concurrent.futures import ThreadPoolExecutor
def rdap(domain):
url = f"https://rdap.org/domain/{domain}" # redirects to the right registry's RDAP server
try:
with urllib.request.urlopen(urllib.request.Request(url, headers={"Accept": "application/rdap+json", "User-Agent": "domain-expiry-script"}), timeout=20) as r:
data = json.load(r)
except urllib.error.HTTPError as e:
if e.code != 404:
return {"domain": domain, "status": f"HTTP {e.code}"}
# 404 means "no RDAP record", which is NOT proof it's free: some registries (.io, .co…) have no RDAP at all
try:
socket.getaddrinfo(domain, None)
return {"domain": domain, "status": "registered (no RDAP)"}
except socket.gaierror:
return {"domain": domain, "status": "maybe free"}
events = {e["eventAction"]: e["eventDate"][:10] for e in data.get("events", [])}
registrar = next((v[3] for ent in data.get("entities", []) if "registrar" in ent.get("roles", [])
for v in ent.get("vcardArray", [None, []])[1] if v[0] == "fn"), "")
return {"domain": domain, "status": "registered", "registrar": registrar,
"created": events.get("registration", ""), "expires": events.get("expiration", "")}
with ThreadPoolExecutor(5) as pool:
for row in pool.map(rdap, sys.argv[1:]):
print(f"{row['domain']:28} {row['status']:20} {row.get('expires', ''):11} {row.get('registrar', '')}")
Real run (October 2026):
$ python3 domain_expiry.py apify.com python.org openalex.org github.io zzqxv-free-name-12345.com
apify.com registered 2035-06-02 Amazon Registrar, Inc.
python.org registered 2033-03-28 Gandi SAS
openalex.org registered 2028-03-14 GoDaddy.com, LLC
github.io registered (no RDAP)
zzqxv-free-name-12345.com maybe free
Three traps I hit writing this
-
The .io trap. My first version printed
github.io → available?, which is obviously wrong. Some registries (.io, .co, and others) don't run RDAP at all, so a 404 means "no RDAP record", not "free". The fix: on 404, check DNS. If the name resolves, it's registered. -
403s from .org. The .org registry rejected requests with no
User-Agent. Always send one. - "Maybe free" really means maybe. No RDAP record and no DNS still isn't proof: names can be reserved, premium, or registered with no DNS. Confirm at a registrar before you get excited.
Also: RDAP servers rate-limit. Keep concurrency low (5 here) for big lists.
Thousands of domains, scheduled
For portfolio checks or weekly expiry monitoring, I put a fuller version on Apify: WHOIS Domain Lookup. It covers RDAP across 1,200+ endings, DNS records (A, MX, NS, TXT), deduplication, and graceful handling of registries without RDAP. It costs $2 per 1,000 domains, and endings without RDAP are DNS-only and free. It never returns registrant contact details.
Either way, the script above is yours.
More free scripts and tools from Kramer Labs.
Top comments (0)