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— queriesqueenof[city].com(primary brand variant)check_queenof_dream.py— queriesqueenof[city]dream.com(experiential variant for tour packages)check_queenof_us.py— restricted to US port cities, checksqueenof[city]usa.com
Each generates a timestamped markdown report:
QUEEN-OF-FRANCHISE-DOMAINS-2026-06-04.md— 130 global port citiesQUEEN-OF-DREAM-DESTINATIONS-2026-06-04.md— alternate brand prefixQUEEN-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).