Skip to content

WHOIS & RDAP Lookup — Python source

Look up registration data for any domain, IP address, or ASN — straight from your browser to the authoritative RDAP registry over HTTPS. No backend, no database of lookups, fully private.

This is the Python implementation — the same logic the interactive tool runs, in a shareable, citable form.

"""RDAP lookup — WHOIS registration data over HTTPS (RFCs 7480-7485).

Language: Python (standard library only)
CosmoDev polyglot showcase port of the ``whois`` tool.
Ported from src/lib/whois.ts — display source, part of CosmoDev's
polyglot tool pages.

RDAP is the JSON successor to port-43 WHOIS. The client first asks
IANA's bootstrap registry which server is authoritative for the TLD
(dns.json) or the ASN block (asn.json), then fetches the record
straight from that server — two HTTPS GETs, no WHOIS port, no backend.
"""

from __future__ import annotations

import json
import re
import urllib.request

BOOTSTRAP_URLS = {
    "domain": "https://data.iana.org/RDAP/dns.json",
    "autnum": "https://data.iana.org/RDAP/asn.json",
}


def classify(query: str) -> dict:
    """Classify a query: an AS number ("as3356" / "3356") or a domain name."""
    m = re.fullmatch(r"(?:as)?(\d{1,10})", query.strip(), re.IGNORECASE)
    if m and int(m.group(1)) > 0:
        return {"kind": "autnum", "value": str(int(m.group(1)))}
    value = query.strip().lower().rstrip(".")
    return {"kind": "domain", "value": value, "tld": value.rsplit(".", 1)[-1]}


def asn_in_range(key: str, asn: int) -> bool:
    """True when ``asn`` falls in an asn.json key ("lo-hi" or a bare number)."""
    if "-" in key:
        lo, hi = (int(n) for n in key.split("-", 1))
        return lo <= asn <= hi
    return int(key) == asn


def fetch_json(url: str):
    """GET a URL over HTTPS and parse the JSON body (RDAP speaks both)."""
    with urllib.request.urlopen(url, timeout=15) as res:
        return json.loads(res.read().decode())


def resolve_rdap_base(q: dict) -> str:
    """Ask IANA's bootstrap which RDAP server is authoritative for the query."""
    services = fetch_json(BOOTSTRAP_URLS[q["kind"]])["services"]
    for keys, urls in services:
        if q["kind"] == "autnum":
            hit = any(asn_in_range(k, int(q["value"])) for k in keys)
        else:
            hit = any(k.lower() == q["tld"] for k in keys)
        if hit:
            return urls[0]
    raise LookupError(f"no RDAP server for {q['value']}")


def whois_lookup(query: str) -> dict:
    """Look up a domain or ASN over RDAP.

    Returns the essentials: registrar (name + email), registration and
    expiration dates, status flags, and nameservers. Raises
    :class:`LookupError` for an unknown TLD/ASN and :class:`OSError` /
    :class:`ValueError` on network or JSON failures.
    """
    q = classify(query)
    base = resolve_rdap_base(q)
    rdap = fetch_json(f"{base.rstrip('/')}/{q['kind']}/{q['value']}")

    # Dates are event objects: [{"eventAction": "registration", ...}, ...]
    dates = {
        e.get("eventAction", "").lower(): e.get("eventDate")
        for e in rdap.get("events", [])
    }
    # The registrar is an entity with role "registrar"; its name and email
    # live inside the jCard array as ["fn", {}, "text", "..."] properties.
    registrar = next(
        (e for e in rdap.get("entities", []) if "registrar" in e.get("roles", [])),
        {},
    )

    def vcard(field: str) -> str | None:
        for prop in registrar.get("vcardArray", [None, []])[1]:
            if prop[0] == field:
                return prop[3]
        return None

    return {
        "registrar": vcard("fn"),
        "registrarEmail": vcard("email"),
        "registered": dates.get("registration"),
        "expires": dates.get("expiration"),
        "status": rdap.get("status", []),
        "nameservers": [ns["ldhName"] for ns in rdap.get("nameservers", []) if "ldhName" in ns],
    }


if __name__ == "__main__":
    print(json.dumps(whois_lookup("cosmolabs.org"), indent=2))

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →