How to Check Domain Availability with RDAP

March 3, 2026

You search for a domain name, and you want to know: is it taken? The traditional approach — connecting to a WHOIS server and parsing free-text output for "No match" or "NOT FOUND" — has always been fragile. Each registrar formats the response differently, and WHOIS is now officially deprecated.

RDAP was not designed as a domain availability API. But it gives you a useful signal — and more context than a simple yes/no check.

How it works

An RDAP server returns HTTP 200 with full JSON when a domain is registered, and HTTP 404 when it is not in the registry. No text parsing, no regex — just a status code.

# Registered domain — returns 200 with JSON
curl -s -o /dev/null -w "%{http_code}" \
  https://rdap.verisign.com/com/v1/domain/google.com
# 200

# Unregistered domain — returns 404
curl -s -o /dev/null -w "%{http_code}" \
  https://rdap.verisign.com/com/v1/domain/xyznotregistered123456.com
# 404

This works for .com because you already know the RDAP server (rdap.verisign.com). For other TLDs, you need bootstrap discovery to find the right server first.

What 404 does not mean

Before writing any code, understand the limits. RDAP tells you whether a domain exists in the registry database right now. It does not tell you whether you can buy it.

  • Reserved names — some TLDs reserve certain strings (single-character domains, geographic names). RDAP returns 404 but the registry will reject your registration
  • Premium pricing — a domain can be available but cost $500+ through the registry. RDAP has no pricing data
  • Sunrise/landrush periods — new TLDs may show 404 but restrict registration to trademark holders
  • Redemption period — a recently deleted domain returns 200 with redemptionPeriod status. It will become available eventually, but you cannot register it yet
  • ccTLD gaps — some country-code TLDs have no RDAP server at all

If you are building a registration workflow and need a definitive available/taken answer that accounts for reserved names and premium pricing, use a registrar availability API (GoDaddy, Namecheap, Dynadot all have one).

RDAP is better when you want context alongside the availability signal — who registered a taken domain, when it expires, which nameservers it uses, which registrar manages it. A 200 response gives you the full registration record, not just "taken."

Using the RDAP API

Querying raw RDAP servers directly means building bootstrap discovery, per-server rate limiting, and error handling yourself. The RDAP API handles that for 1,200+ TLDs.

A registered domain returns normalized JSON:

curl -s -H "Authorization: Bearer YOUR_API_KEY" \
  "https://rdapapi.io/api/v1/domain/google.com"
{
  "domain": "google.com",
  "status": ["client delete prohibited", "server transfer prohibited"],
  "registrar": { "name": "MarkMonitor Inc." },
  "dates": {
    "registered": "1997-09-15T04:00:00Z",
    "expires": "2028-09-14T04:00:00Z"
  },
  "nameservers": ["ns1.google.com", "ns2.google.com"]
}

An unregistered domain returns a 404:

{
  "error": "not_found",
  "message": "This domain was not found. It may not be registered."
}

Multi-TLD availability checker

The practical use case: you have a brand name and want to check which TLDs are still available. All five SDKs throw a typed NotFoundError on 404, so you catch it to determine availability. When a domain is taken, you also get its expiration date — useful for watching domains you want.

import os
from rdapapi import RdapApi, NotFoundError  # pip install rdapapi

api = RdapApi(os.environ["RDAP_API_KEY"])

base = "acmetools"
tlds = [".com", ".io", ".dev", ".app", ".net", ".org"]

for tld in tlds:
    domain = base + tld
    try:
        data = api.domain(domain)
        expires = data.dates.expires or "unknown"
        print(f"  {domain} — taken (expires {expires})")
    except NotFoundError:
        print(f"  {domain} — probably available")
import { RdapClient, NotFoundError } from "rdapapi"; // npm install rdapapi

const api = new RdapClient(process.env.RDAP_API_KEY);

const base = "acmetools";
const tlds = [".com", ".io", ".dev", ".app", ".net", ".org"];

for (const tld of tlds) {
  const domain = base + tld;
  try {
    const data = await api.domain(domain);
    const expires = data.dates.expires || "unknown";
    console.log(`  ${domain} — taken (expires ${expires})`);
  } catch (e) {
    if (e instanceof NotFoundError) {
      console.log(`  ${domain} — probably available`);
    } else {
      throw e;
    }
  }
}
// composer require rdapapi/rdapapi-php
$api = new \RdapApi\RdapApi(getenv('RDAP_API_KEY'));

$base = 'acmetools';
$tlds = ['.com', '.io', '.dev', '.app', '.net', '.org'];

foreach ($tlds as $tld) {
    $domain = $base . $tld;
    try {
        $data = $api->domain($domain);
        $expires = $data->dates->expires ?? 'unknown';
        echo "  {$domain} — taken (expires {$expires})\n";
    } catch (\RdapApi\Exceptions\NotFoundException $e) {
        echo "  {$domain} — probably available\n";
    }
}
// go get github.com/rdapapi/rdapapi-go
client := rdapapi.NewClient(os.Getenv("RDAP_API_KEY"))

base := "acmetools"
tlds := []string{".com", ".io", ".dev", ".app", ".net", ".org"}

for _, tld := range tlds {
    domain := base + tld
    data, err := client.Domain(domain, nil)
    if err != nil {
        var notFound *rdapapi.NotFoundError
        if errors.As(err, &notFound) {
            fmt.Printf("  %s — probably available\n", domain)
            continue
        }
        fmt.Printf("  %s — error: %s\n", domain, err)
        continue
    }
    expires := "unknown"
    if data.Dates.Expires != nil {
        expires = *data.Dates.Expires
    }
    fmt.Printf("  %s — taken (expires %s)\n", domain, expires)
}
// io.rdapapi:rdapapi-java (Maven Central)
RdapClient client = new RdapClient(System.getenv("RDAP_API_KEY"));

String base = "acmetools";
List<String> tlds = List.of(".com", ".io", ".dev", ".app", ".net", ".org");

for (String tld : tlds) {
    String domain = base + tld;
    try {
        DomainResponse data = client.domain(domain);
        String expires = data.getDates().getExpires() != null
            ? data.getDates().getExpires() : "unknown";
        System.out.printf("  %s — taken (expires %s)%n", domain, expires);
    } catch (NotFoundException e) {
        System.out.printf("  %s — probably available%n", domain);
    }
}

Six domains = six API calls. On the Starter plan (30,000 lookups/month, $9), you can check 5,000 base names across 6 TLDs per month.

Bulk checking

For higher volume, the bulk endpoint accepts up to 10 domains per request. Domains that are not found return inline errors — no try/catch per domain:

curl -X POST -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["google.com", "xyznotregistered123456.com"]}' \
  "https://rdapapi.io/api/v1/domains/bulk"
{
  "results": [
    {
      "domain": "google.com",
      "status": "success",
      "data": { "domain": "google.com", "registrar": { "name": "MarkMonitor Inc." }, "..." : "..." }
    },
    {
      "domain": "xyznotregistered123456.com",
      "status": "error",
      "error": "not_found",
      "message": "This domain was not found. It may not be registered."
    }
  ],
  "summary": { "total": 2, "successful": 1, "failed": 1 }
}

A result with status: "error" and error: "not_found" means the domain is probably available. Bulk requires a Pro or Business plan.

A note on privacy

When you query raw RDAP (the curl examples above), your request goes directly to the registry — Verisign, Google Registry, Identity Digital. Registries do not sell domains to end users, so they have no incentive to act on your queries.

Registrar availability APIs route through the registrar, which does sell domains. Any third-party API — including ours — is also a middleman that can see your lookups. If you are researching domain ideas and privacy matters, raw RDAP is the most private option.

Further reading


Ready to try RDAP lookups?