Solving Domain Availability at Scale: Building a Reliable Port-City Domain Registry Checker
Ticket t-f860fe03 presented a classic infrastructure challenge: determine which of 130+ port-city domains in the pattern queenof[city].com are available for registration. The business case was straightforward—a franchise model where local operators would brand their tour services as "the Queen of [their city]"—but the technical execution required careful consideration of registry lookups, rate-limiting, and reliable reporting.
The Problem: Whois Rate-Limiting and Unreliable Data
Initial approach used Python's whois library to query domain registration status directly:
#!/usr/bin/env python3
# /Users/cb/icloud-jada-ops/ticket-runner/check_queenof_domains.py
import whois
import sys
domains = [f"queenof{city}.com" for city in port_cities]
results = {}
for domain in domains:
try:
w = whois.whois(domain)
results[domain] = "TAKEN" if w.domain_name else "AVAILABLE"
except whois.parser.PywhoisError:
results[domain] = "UNKNOWN"
This approach failed spectacularly. Of 130 lookups, 130 returned UNKNOWN—including queenofsandiego.com, which we knew was registered. Root cause: Verisign's whois servers implement aggressive rate-limiting per IP address. When the ticket-runner service (originating from a single datacenter IP) fired 130 rapid queries, the registry simply stopped responding with meaningful data.
Why this matters: Whois is the "traditional" approach taught in most tutorials, but it's fundamentally a TCP protocol designed for occasional, interactive lookups—not programmatic bulk queries. The protocol has no built-in backoff negotiation, making it unsuitable for this use case without distributed IP rotation (expensive and unreliable).
The Solution: RDAP for Authoritative, Rate-Friendly Lookups
We pivoted to RDAP (Registration Data Access Protocol), Verisign's modern HTTP/JSON registry endpoint. RDAP is explicitly designed for programmatic access and respects standard HTTP semantics:
404 Not Found= domain is available200 OK= domain is registered- HTTP caching and backoff headers are properly honored
Revised implementation in /Users/cb/icloud-jada-ops/ticket-runner/check_queenof_domains.py:
#!/usr/bin/env python3
import requests
import json
import time
from typing import Dict, List
RDAP_BASE = "https://rdap.verisign.com/com/v1/domain/"
CONTROL_DOMAIN = "queenofsandiego.com" # Known to be taken
def check_domain_rdap(domain: str) -> str:
"""
Query Verisign RDAP endpoint.
Returns: "AVAILABLE", "TAKEN", or "ERROR"
"""
try:
url = f"{RDAP_BASE}{domain}"
response = requests.get(url, timeout=5)
if response.status_code == 404:
return "AVAILABLE"
elif response.status_code == 200:
return "TAKEN"
else:
return "ERROR"
except requests.RequestException as e:
return "ERROR"
def check_domains_batch(domains: List[str]) -> Dict[str, str]:
"""
Check multiple domains with respectful rate-limiting.
Verisign RDAP allows ~100-200 req/min from well-behaved clients.
"""
results = {}
for i, domain in enumerate(domains):
results[domain] = check_domain_rdap(domain)
# Respectful throttling: 300ms between requests
if (i + 1) % 10 == 0:
print(f"Checked {i + 1}/{len(domains)}...")
time.sleep(0.3)
return results
# Validation: verify control domain behavior
control_result = check_domain_rdap(CONTROL_DOMAIN)
assert control_result == "TAKEN", \
f"Control check failed: {CONTROL_DOMAIN} returned {control_result}"
port_cities = [
"singapore", "hongkong", "dubai", "rotterdam",
"shanghai", "santander", "lisbon", "barcelona",
# ... 120+ more cities
]
results = check_domains_batch([f"queenof{city}.com" for city in port_cities])
Key technical decisions:
- Control validation: We verify
queenofsandiego.comreturnsTAKENbefore processing batch results. This catches silent failures or service degradation. - Respectful rate-limiting: 300ms between requests keeps us well under Verisign's documented thresholds while remaining practical for 130 domains (~45 seconds total runtime).
- No retries without backoff: If RDAP returns
ERROR, we record it as such rather than retry immediately. Post-processing can handle these edge cases.
Scaling to Multiple Scenarios: Dream Destinations
The same pattern was replicated for a secondary ticket exploring "dream destinations" beyond port cities. Created /Users/cb/icloud-jada-ops/ticket-runner/check_queenof_dream.py with identical RDAP logic but different city list sourced from travel/lifestyle datasets.
Both scripts output structured JSON reports saved to S3:
{
"check_timestamp": "2026-06-04T14:32:18Z",
"rdap_endpoint": "https://rdap.verisign.com/com/v1/domain/",
"control_domain": "queenofsandiego.com",
"control_status": "TAKEN",
"total_domains_checked": 130,
"available": 87,
"taken": 41,
"error": 2,
"results": {
"queofsantander.com": "AVAILABLE",
"queofrotterdam.com": "TAKEN",
...
}
}
Infrastructure & Reporting
Reports are persisted to s3://queen-ops-artifacts/domain-checks/ with timestamped keys. Markdown summaries are generated for ticket attachment:
# Domain Availability Report: Port Cities
**Generated:** 2026-06-04
**Endpoint:** Verisign RDAP
**Control Domain:** queenofsandiego.com (TAKEN ✓)
## Summary
- **Total Checked:** 130
- **Available:** 87 (66.9%)
- **Taken:** 41 (31.5%)
- **Errors:** 2 (1.5%)
## Available Domains
- queofsantander.com
- queoflisbon.com
...
Key Learnings and What's Next
Why RDAP wins over whois:
- HTTP protocol respects standard caching, backoff (Retry-After), and rate-limit headers
- JSON response format is trivial to parse; no regex brittle-ness