All Notes
All Notes

Built a domain-checking skill for Claude Code using RDAP. String matching with whois kept failing. Structured protocols solve it.

#domain-checking#skills#automation#claude-code#rdap#whois

Domain Checking Skill#

The Symptom#

I wanted Claude Code to check if domains were available. Simple enough—just query WHOIS and grep for “available” or “no match”, right?

Except it kept giving wrong answers. Some domains showed as available when they weren’t. Others showed as registered when they were actually free.

The pattern matching worked fine for .com domains most of the time, but fell apart on .io, .sh, and other TLDs. Different registries return completely different response formats.

The Investigation#

I tried improving the regex patterns. Added more variations of “not found” and “no match”. Accounted for case sensitivity. Built a lookup table of registry-specific strings.

bash
# .com domains
$ whois example.com | grep -i "no match"
No match for domain "EXAMPLE.COM".

# .io domains
$ whois example.io | grep -i "no match"
# (returns nothing - uses "NOT FOUND" instead)

# .sh domains
$ whois example.sh | grep -i "no match"
# (returns nothing - uses "No entries found" instead)

Here’s what I found across just a handful of TLDs:

TLDAvailable StringCase Sensitive
.com”No match for domain”No
.net”No match for domain”No
.org”NOT FOUND”Yes
.io”NOT FOUND”Yes
.sh”No entries found”No
.uk”No match for”No
.dev”Domain not found”No

And that’s just for available domains. Registered domains have even more variation—some show “Registrant”, others show “Domain Status”, and some just dump raw registry data.

I could keep building this lookup table for all 1,500+ TLDs, but that’s maintenance hell. Every time a registry changes their format, the grep patterns break.

The Root Cause#

WHOIS is unstructured text designed for humans to read. It was created in 1982, long before JSON or structured APIs. Each registry formats responses however they want.

String parsing across heterogeneous systems is fragile. You’re matching against free-form text that can change at any time. There’s no schema, no standardization, no validation.

Even worse, most WHOIS tools query third-party aggregators, not official registries. You’re trusting intermediaries who may cache data, inject ads, or shape responses differently.

RDAP Protocol#

RDAP (Registration Data Access Protocol) is the modern replacement for WHOIS. It’s an IETF standard (RFC 7480) that returns structured JSON instead of text.

Instead of parsing “No match for domain”, you get:

json
{"errorCode": 404, "title": "Not Found"}

Instead of grep-ing through paragraphs for “Registrant”, you get:

json
{
  "status": [
    "client delete prohibited",
    "client transfer prohibited"
  ]
}

The rdap.org bootstrap service handles TLD routing automatically. You query one endpoint, it figures out which registry owns that TLD, and forwards the request. No lookup tables needed.

The Implementation#

Here’s the actual RDAP query:

bash
# Check if a domain is available
HTTP_CODE=$(curl -sL -o response.json -w "%{http_code}" "https://rdap.org/domain/example.com")

if [ "$HTTP_CODE" = "404" ]; then
  echo "AVAILABLE"
else
  jq -r '.status[]' response.json
fi

Compare this to the WHOIS nightmare:

bash
# WHOIS: fragile string matching
if whois example.com | grep -qi "no match\|not found\|no entries"; then
  echo "AVAILABLE"
else
  echo "REGISTERED"
fi

The RDAP approach works identically across all TLDs. No special cases, no regex tuning, no false positives.

Here’s how the flow works:

alt [Domain Registered][Domain Available]Query example.comRoute to Verisign RDAPJSON responseStructured dataParse TLD (.com)HTTP 200<br/>status: ["client delete prohibited"]HTTP 404<br/>errorCode: 404Claude Coderdap.orgTLD Registry

The bootstrap service eliminates the need for TLD-to-registry mappings. It handles routing, so you don’t have to know that .com goes to Verisign, .io goes to the Internet Computer Bureau, and .sh goes to Afilias.

The Pattern#

I built this as a Claude Code skill—instructions-only, no npm packages. It uses curl and jq, which are already installed on macOS/Linux.

Single domain check:

bash
curl -sL "https://rdap.org/domain/example.com" | jq -r '.status // "AVAILABLE"'

Batch checking:

bash
for domain in startup.io startup.sh startup.dev; do
  (
    HTTP_CODE=$(curl -sL -o /dev/null -w "%{http_code}" "https://rdap.org/domain/$domain")
    if [ "$HTTP_CODE" = "404" ]; then
      echo "✓ $domain is AVAILABLE"
    else
      echo "✗ $domain is REGISTERED"
    fi
  ) &
done
wait

The & runs each check in the background, and wait waits for all to finish. You can check 10 domains in parallel instead of sequentially.

Integration with Claude Code:

The skill activates when you ask questions like “Check if example.com is available” or “Is startup.io registered?” Claude uses RDAP automatically instead of grep-ing through WHOIS output.

No external APIs, no auth tokens, no third-party services. Just direct queries to official registries via a standardized protocol.

Bulk Checking in Practice#

Testing the skill on 10 random domains simultaneously:

bash
for domain in \
  random-test-xyz-12345.com \
  super-ultra-mega-rare-domain-999.io \
  claude-code-rocks.dev \
  totally-available-probably.sh \
  anthropic.com \
  openai.com \
  vercel.com \
  cloudflare.com \
  best-startup-name-ever-2026.ai \
  my-cool-project-abc.net; do
  (
    HTTP_CODE=$(curl -sL -o /dev/null -w "%{http_code}" "https://rdap.org/domain/$domain")
    if [ "$HTTP_CODE" = "404" ]; then
      echo "✓ $domain is AVAILABLE"
    else
      echo "✗ $domain is REGISTERED"
    fi
  ) &
done
wait

Results in under 2 seconds:

✓ random-test-xyz-12345.com is AVAILABLE
✓ super-ultra-mega-rare-domain-999.io is AVAILABLE
✓ claude-code-rocks.dev is AVAILABLE
✓ totally-available-probably.sh is AVAILABLE
✓ best-startup-name-ever-2026.ai is AVAILABLE
✓ my-cool-project-abc.net is AVAILABLE
✗ anthropic.com is REGISTERED
✗ openai.com is REGISTERED
✗ vercel.com is REGISTERED
✗ cloudflare.com is REGISTERED

For registered domains, you can get detailed status:

bash
curl -sL "https://rdap.org/domain/anthropic.com" | jq -r '.status[]?'
client delete prohibited
client transfer prohibited
client update prohibited

With registration dates:

bash
curl -sL "https://rdap.org/domain/cloudflare.com" | \
  jq -r '.events[]? | select(.eventAction == "registration") | .eventDate'
2009-02-17T22:07:54Z

The parallel approach scales to dozens of domains. For bulk checking (50+ domains), add rate limiting:

bash
for domain in "${domains[@]}"; do
  # Check domain
  curl -sL "https://rdap.org/domain/$domain" | jq -r '.status // "AVAILABLE"'
  sleep 0.5  # 500ms delay to avoid rate limits
done

If you hit HTTP 429 (rate limited), the skill handles it gracefully. RDAP registries typically allow 10-20 requests per second, far more than WHOIS aggregators.

What I Learned#

String parsing is fragile across heterogeneous systems. When every data source formats responses differently, pattern matching becomes whack-a-mole. You fix one TLD, break another, repeat forever.

Structured protocols reduce maintenance burden. RDAP returns JSON with standardized fields across all registries. The schema doesn’t drift. The error codes are consistent. You write the parsing logic once.

Privacy and reliability don’t have to trade off. RDAP queries go directly to official registries, not third-party aggregators. You get authoritative data without leaking query patterns to data brokers.

And here’s the part I didn’t expect: the instructions-based skill pattern works better than a CLI package for simple tasks. curl and jq are already installed. No npm install, no version conflicts, no maintenance overhead. The skill just works.

Sometimes the right abstraction is a protocol, not a library.

Built a domain-checking skill for Claude Code using RDAP. String matching with whois kept failing. Structured protocols solve it.