Guides

RDAP vs WHOIS for abuse contacts: what changed

For thirty years the way to find who was behind a domain or an IP address was to open a TCP connection to port 43 and read whatever text came back. Every registry formatted it differently, most changed the format without notice, and anyone who automated it maintained a pile of regular expressions that broke monthly. RDAP replaced that with JSON over HTTPS. For generic top-level domains the replacement is complete: ICANN required registries and registrars to run RDAP from August 2019, and the contractual requirement to run WHOIS ended on 28 January 2025. Tools that still print port 43 output for .com are showing you a cache.

This guide is about using RDAP for the one thing abuse teams need from it: the abuse contact.

The protocol in one paragraph

RDAP is defined in RFC 7480 to 7484, updated by RFC 9082 (queries) and RFC 9083 (responses). A query is an HTTPS GET to <base>/domain/<name>, <base>/ip/<address> or <base>/autnum/<asn>. The response is a JSON object with an objectClassName, some identifying fields, an events array (registration, last changed, expiration), a status array, links, remarks, and an entities array describing the parties involved. Contacts inside entities are expressed as jCard (RFC 7095), which is vCard rendered as nested JSON arrays. Servers may answer with HTTP redirects to a more authoritative server, and clients are expected to follow them.

Finding the right server

There is no single RDAP server. IANA publishes bootstrap files (RFC 9224) that map top-level domains, IP ranges and AS number ranges to base URLs:

  • https://data.iana.org/rdap/dns.json
  • https://data.iana.org/rdap/ipv4.json
  • https://data.iana.org/rdap/ipv6.json
  • https://data.iana.org/rdap/asn.json

Fetch these once a day, match the longest prefix, and you have the base URL. If you would rather not, the community redirector at rdap.org does the matching and answers with a redirect:

curl -sIL https://rdap.org/ip/193.0.6.139 | grep -i '^location'

Reading a domain response

curl -s https://rdap.org/domain/example.org | python3 -m json.tool

The parts that matter:

  • entities[] with "roles": ["registrar"]: the sponsoring registrar. publicIds carries its IANA Registrar ID, a stable number that survives the registrar renaming itself.
  • Inside that registrar entity, entities[] with "roles": ["abuse"]: the abuse contact. In its vcardArray, the email and tel properties are the address and phone the registrar is obliged to publish.
  • events[]: registration tells you the domain's age, which is the single most useful fact for judging whether the domain is malicious or compromised.
  • status[]: values such as client hold or server hold mean the domain is already suspended and not resolving.
  • nameservers[]: where the domain's DNS is delegated.

For .com and .net the registry (Verisign) holds only registrar and nameserver data. The abuse contact may be present, or you may need to follow the links entry with "rel": "related" to the registrar's own RDAP server and read the same structure there. Other gTLD registries return the full record directly.

Registrant, administrative and technical contacts are usually redacted under GDPR and equivalent laws, and for abuse routing that does not matter. The registrar's abuse mailbox is the one that acts.

Reading an IP response

curl -sL https://rdap.org/ip/203.0.113.10 | python3 -m json.tool

The object is an ip network: startAddress, endAddress, name (the netname), country, type (allocation or assignment), and entities. The entity with "roles": ["abuse"] is the network's abuse contact, again in jCard form. Where a registry uses the cidr0 extension you will also see cidr0_cidrs with the prefix and length, convenient for logging.

Each RIR adds its own flavour:

  • ARIN returns the abuse point of contact as an entity with a handle ending in -ARIN. ARIN also refuses more than one simultaneous new TLS connection from a single source, so clients that open connections in parallel see connection resets rather than HTTP 429s. Query sequentially over one persistent connection.
  • RIPE NCC models the contact through abuse-c, a role object that is mandatory on every allocation. Person and role objects that contain personal data are rate-limited per source address; the abuse role is exempt, but a full /ip response embeds administrative and technical persons that are not. If you only want the abuse mailbox, RIPE's REST search with flags=no-referenced&abuse-contact=true returns it without the personal data and without touching the limit.
  • APNIC uses an IRT (incident response team) object whose abuse-mailbox is the address to use, and treats it as commercial rather than personal data.
  • LACNIC delegates national address space, so rdap.lacnic.net answers with a 307 redirect to rdap.registro.br for Brazilian addresses and to NIC Mexico for Mexican ones. Follow the redirect. registro.br also sends the cidr0 prefix length as a string rather than a number, which will break a strictly typed parser.
  • AFRINIC follows the RIPE model closely.

Quirks that break naive parsers

  • jCard. A contact is ["vcard", [["version", {}, "text", "4.0"], ["fn", {}, "text", "Abuse Desk"], ["email", {}, "text", "abuse@example.net"]]]. Property values are the fourth element of each inner array, and structured values (addresses) are arrays themselves.
  • Multiple emails. An abuse entity can carry more than one email property, sometimes with a pref parameter. Take them all.
  • Missing roles. Some registrars return the abuse contact as a top-level entity rather than nested under the registrar. Check both places.
  • Type inconsistencies. Numbers as strings, as noted for registro.br; booleans as strings elsewhere. Parse leniently.
  • Redirects with a different path shape. After a 307, the new URL may use a different path convention. Follow the Location header as given.
  • 404 for unregistered domains. RDAP returns 404 when a domain does not exist. Distinguish that from a registry that exists but returns no useful data.

Country-code domains

ccTLDs were never bound by ICANN's RDAP mandate. Many have deployed it anyway, but in their own time and on their own terms. Some publish full records, some publish registrar name only, some publish nothing useful about the registrar, and one or two attach terms of use that restrict automated or commercial use of the data. Where a ccTLD has no RDAP, port 43 WHOIS is still the source and still needs a per-registry parser. Where it has neither, the hosting network contact is still available through the RIRs, and that is the contact that removes content anyway.

Being a good client

Registries run RDAP for free and can withdraw it from anyone who abuses it. Three habits keep you welcome:

  1. Identify yourself. A User-Agent naming your organisation and a URL. Anonymous bulk traffic is what registries block first.
  2. Cache. Network allocations change rarely; a day is a safe cache for /ip responses. Registrar data changes more often but an hour is fine. Never persist and re-serve the data as a database; that crosses from use into compilation, which every registry's terms prohibit.
  3. Go slowly. One request at a time per registry over a kept-alive connection is faster in practice than parallel connections that get refused, and it is what the registries ask for.

Where a registry's terms restrict commercial use, as ARIN's and AFRINIC's do, ask them in writing before relying on the data in a paid service. In our experience they answer, and quickly, when the use is the one the abuse contact was published for.


AbuseRoute is a private API that returns the registrar and hosting abuse contacts for a domain in one call, for security and abuse teams. How it works. For individual manual lookups, use the RrSG's ACID Tool.