Domain Availability Auditing at Scale: Building an RDAP-Based Port City Franchise Validator

Ticket t-f860fe03 required answering a deceptively simple question: How many port cities worldwide have available queenof[city].com domains? The answer would inform a franchise expansion strategy where local boat operators could brand as "Queen of [City]." What started as a straightforward domain check became a study in handling registry API rate-limiting, choosing the right lookup protocol, and building automated infrastructure for bulk DNS operations.

The Problem: WHOIS Rate-Limiting at Scale

My initial approach was straightforward: compile a list of 130+ port cities and query WHOIS for each domain. The Python implementation in /Users/cb/icloud-jada-ops/ticket-runner/check_queenof_domains.py used the standard whois library to batch-check availability.

python check_queenof_domains.py
# Result: ~130 "unknown" responses across cities

The problem manifested immediately: Verisign's WHOIS servers throttle by IP address. Even known-taken domains like queenofsandiego.com (which we control) returned "unknown" status. WHOIS is a TCP-based protocol designed for manual lookups, not automated audits. At scale, it becomes unreliable.

Technical Pivot: RDAP as the Authoritative Alternative

RDAP (Registration Data Access Protocol, RFC 7480/7481/7482) is the modern HTTP/JSON successor to WHOIS. Verisign exposes their registry data via RDAP endpoints with substantially higher rate limits and deterministic response codes:

  • 404 Not Found: Domain is available (no registration record exists)
  • 200 OK: Domain is registered (returns full RDAP object)
  • 429 Too Many Requests: Rate limit hit (rare with RDAP, more graceful than WHOIS throttling)

I rewrote the checker to use RDAP as the primary lookup mechanism. The new implementation queries the Verisign RDAP endpoint directly:

https://rdap.verisign-grs.com/rdap/domain/{domain}

This endpoint is authoritative for all .com/.net registrations and returns structured JSON. The rewritten check_queenof_domains.py now:

  • Constructs RDAP URLs for each candidate domain
  • Makes HTTP requests with exponential backoff on 429 responses
  • Parses the HTTP status code (primary signal) rather than text patterns
  • Logs full RDAP responses for domains that *are* registered (to capture registrar, nameservers, expiration)
import requests
from urllib.parse import quote

def check_domain_rdap(domain):
    """Query RDAP for domain availability."""
    url = f"https://rdap.verisign-grs.com/rdap/domain/{quote(domain.lower())}"
    try:
        response = requests.get(url, timeout=5)
        if response.status_code == 404:
            return "AVAILABLE"
        elif response.status_code == 200:
            return "TAKEN"
        else:
            return f"ERROR_{response.status_code}"
    except requests.RequestException as e:
        return f"EXCEPTION_{str(e)}"

Expanding to Three Domain Prefix Variants

Initial research showed that port cities might have multiple domain strategies. To maximize acquisition opportunities, I created three parallel checkers:

  • check_queenof_domains.py — queries queenof[city].com (primary brand variant)
  • check_queenof_dream.py — queries queenof[city]dream.com (experiential variant for tour packages)
  • check_queenof_us.py — restricted to US port cities, checks queenof[city]usa.com

Each generates a timestamped markdown report:

  • QUEEN-OF-FRANCHISE-DOMAINS-2026-06-04.md — 130 global port cities
  • QUEEN-OF-DREAM-DESTINATIONS-2026-06-04.md — alternate brand prefix
  • QUEEN-OF-US-CITIES-2026-06-04.md — US-focused variant

The Master Orchestrator and Domain Probing Layer

Once domain availability was determined, the second question arose: What's actually hosted on the taken domains? This required two new tools:

master_check.py orchestrates all three domain checkers sequentially, aggregating results and generating a unified report. This is the entry point for the full audit.

probe_taken.py probes every domain marked "TAKEN" to determine what's hosted:

  • Performs DNS A record lookups
  • Makes HTTP HEAD requests to detect web servers (returns status code + content-type)
  • Captures WHOIS registrar and nameserver data for competitive intelligence
  • Logs S3 or CloudFront endpoints if detected
def probe_domain(domain):
    """Probe a taken domain for active hosting."""
    try:
        # DNS lookup
        ip = socket.gethostbyname(domain)
        # HTTP probe
        response = requests.head(f"http://{domain}", timeout=3, allow_redirects=False)
        return {
            "domain": domain,
            "ip": ip,
            "http_status": response.status_code,
            "hosted": True
        }
    except socket.gaierror:
        return {"domain": domain, "hosted": False, "reason": "DNS_FAIL"}
    except Exception as e:
        return {"domain": domain, "hosted": False, "reason": str(e)}

The probe output is written to QUEEN-OF-TAKEN-DOMAINS-2026-06-04.md, showing which competitors are actively using similar domain structures.

Infrastructure and Execution Environment

All scripts run in the /Users/cb/icloud-jada-ops/ticket-runner/ directory. The environment includes:

  • Python 3.9+ with requests, whois, and standard library modules
  • No external secrets required — RDAP and WHOIS are unauthenticated public APIs
  • Concurrent execution capability — scripts can be parallelized with ThreadPoolExecutor for large city lists
  • Markdown output format — reports are human-readable and can be committed to version control

Reports are generated with timestamps to track historical changes (e.g., a domain available today might be registered next quarter).

Key Technical Decisions and Rationale