```html

Automating Domain Availability Research at Scale: Building a Multi-Registry Probe System for the Queen-of-Cities Franchise

Overview: From Manual WHOIS to Intelligent Registry Probing

Ticket t-f860fe03 presented a domain research challenge: determine how many port cities worldwide have available queenof[city].com domains for a franchise concept where local tour operators' vessels become the "Queen of that city." The initial approach using direct WHOIS queries hit rate-limiting walls almost immediately. This post documents how we built a resilient multi-registry probing system that combines WHOIS fallback, RDAP (Registration Data Access Protocol) as the primary authoritative source, and structured result aggregation.

The Problem: WHOIS Rate-Limiting and Reliability

The naive solution—hitting WHOIS servers directly for ~180 port cities—failed within the first 50 queries. Verisign, the .com registry operator, throttles WHOIS connections aggressively when too many rapid-fire queries originate from a single IP. Worse, throttled responses return "unknown" status rather than failing cleanly, making it impossible to distinguish between "available" and "rate-limited."

Even our control domain (queenofsandiego.com, which we knew was registered) returned "unknown" under throttling, invalidating the entire result set.

Technical Architecture: Multi-Layer Registry Probing

Layer 1: RDAP as Primary Authority

We switched to RDAP, Verisign's HTTP/JSON registry endpoint. Unlike WHOIS, RDAP:

  • Returns clean HTTP status codes: 404 Not Found = available, 200 OK = registered
  • Uses REST semantics familiar to developers
  • Is far less aggressively throttled (designed for programmatic queries)
  • Provides structured JSON responses for easier parsing

The RDAP endpoint for .com domains is https://rdap.verisign.com/com/v1/domain/[domain].

Layer 2: Structured Python Wrapper

We created /Users/cb/icloud-jada-ops/ticket-runner/check_queenof_domains.py as the core probe module. The architecture:

def check_domain_rdap(domain: str) -> dict:
    """Query RDAP endpoint; return {domain, status, registry_response}"""
    url = f"https://rdap.verisign.com/com/v1/domain/{domain}"
    try:
        response = requests.get(url, timeout=10)
        if response.status_code == 404:
            return {"domain": domain, "status": "AVAILABLE"}
        elif response.status_code == 200:
            return {"domain": domain, "status": "TAKEN"}
        else:
            return {"domain": domain, "status": "UNKNOWN", "http_code": response.status_code}
    except Exception as e:
        return {"domain": domain, "status": "ERROR", "error": str(e)}

Key design decisions:

  • Timeout enforcement: 10-second timeout prevents hanging on slow registry responses
  • Status normalization: HTTP codes map to semantic domain states (AVAILABLE, TAKEN, UNKNOWN)
  • Error isolation: Network errors don't crash the batch; they're logged and counted separately
  • Structured output: Every result is a dict with domain, status, and optional metadata—enabling downstream aggregation

Layer 3: Franchise-Specific Probe Scripts

We created three specialized probe scripts, each targeting a different franchise concept:

  • check_queenof_domains.py — Port cities (original ticket scope)
  • check_queenof_dream.py — Dream destinations (aspirational travel)
  • check_queenof_us.py — US cities only (domestic market)

Each script:

  • Imports a city list from an external source (JSON, hardcoded tuple, or API)
  • Normalizes city names to domain-safe slugs (spaces → hyphens, accents removed)
  • Launches parallel RDAP queries (using Python's concurrent.futures.ThreadPoolExecutor) to avoid sequential bottleneck
  • Aggregates results into a summary report with counts by status

Example structure from check_queenof_us.py:

cities = [
    "new-york", "los-angeles", "chicago", "houston",
    "phoenix", "philadelphia", "san-antonio", "san-diego",
    # ... 50+ cities total
]

def main():
    results = probe_domains(cities, prefix="queenof")
    available = [r for r in results if r["status"] == "AVAILABLE"]
    taken = [r for r in results if r["status"] == "TAKEN"]
    
    print(f"\nSummary: {len(available)} available, {len(taken)} taken")
    # Write report to dated markdown file

Master Orchestration and Taken-Domain Analysis

We created master_check.py to run all three probes in sequence and consolidate results. This reveals franchise viability across different market segments.

A secondary insight: knowing which domains are taken is valuable. We built probe_taken.py to do HTTP/DNS reconnaissance on registered-but-possibly-parked domains—determining whether they're:

  • Active web properties (HTTP 200)
  • DNS-configured parking pages (HTTP 200 from registrar)
  • Dormant (DNS exists but no web service)
  • Fully abandoned (DNS missing)

This data informs acquisition strategy: some "taken" domains might be negotiable if they're parked and not actively monetized.

Output: Structured Markdown Reports

Each probe run generates a dated markdown report:

  • QUEEN-OF-FRANCHISE-DOMAINS-2026-06-04.md — Port cities probe results
  • QUEEN-OF-DREAM-DESTINATIONS-2026-06-04.md — Dream destinations results
  • QUEEN-OF-US-CITIES-2026-06-04.md — US cities results
  • QUEEN-OF-TAKEN-DOMAINS-2026-06-04.md — Hosting analysis of registered domains

Report format includes:

# Queen-of Port Cities Domain Availability

**Scan Date:** 2026-06-04  
**Total Cities Probed:** 130  
**Available Domains:** 87  
**Taken Domains:** 43  
**Errors:** 0  

## Available Domains (87)
- queenof-amsterdam.com
- queenof-barcelona.com
- queenof-hong-kong.com
[... sorted list ...]

## Taken Domains (43)
- queenofsandiego.com (ACTIVE web property)
- queenofliverpool.com (DNS only, likely parked)
[... with hosting status ...]

Key Architectural