RDAP
RFC 9083 registry data over HTTPS — the machine-readable who-owns-this.
The pain
WHOIS never had a schema. Every registry formats its whois -h whois.example.net reply a little differently — free text, inconsistent field labels, no content type, no pagination, and rate limits enforced by guessing at your User-Agent. If you've ever written a regex to scrape a registration date out of a WHOIS blob, or watched it silently break when a registry re-wrapped a line, you already know why this had to be fixed. An agent deciding in milliseconds whether to trust a peer's /128 cannot afford to screen-scrape prose. It needs a typed object, a stable content type, and an HTTP status code it can branch on — which is exactly what RDAP is, and exactly why every one of Whisper's 2a04:2a01::/32 agent addresses is a first-class RDAP object, not an afterthought bolted onto DNS.
What RDAP is
RDAP (the Registration Data Access Protocol) is WHOIS's IETF-standardized replacement: plain HTTPS GET requests answered with application/rdap+json bodies. The core specs:
- RFC 7480 — RDAP runs over HTTP(S); ordinary GETs, ordinary status codes, ordinary content negotiation.
- RFC 9082 — the query syntax: predictable, hierarchical paths like
/ip/<addr>and/domain/<name>, no registry-specific query language to learn. - RFC 9083 — the JSON response format: a fixed object model (
objectClassName,entities,events,links,status,notices) shared by every conformant RDAP server on the internet, from ARIN to RIPE to us. - RFC 9224 — the bootstrap mechanism a generic client uses to find the right RDAP server for a given resource, by walking IANA's published bootstrap registries.
Whisper runs a conformant RDAP server at rdap.whisper.online for the address space we hold. Point a generic RDAP client at any of our /128s or agent names and it just works — no Whisper-specific parsing required.
The object model
Every response opens with rdapConformance (the RDAP extensions/levels in play) and an objectClassName that tells a generic client what shape to expect. For an agent's /128, that's "ip network":
{
"rdapConformance": ["rdap_level_0"],
"objectClassName": "ip network",
"handle": "2A04:2A01:EB5A:CA74:CEF2:2A:323D:40D4/128",
"startAddress": "2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4",
"endAddress": "2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4",
"ipVersion": "v6",
"name": "WHISPER-AGENT-323D40D4",
"type": "ASSIGNED",
"country": "NL",
"parentHandle": "2A04:2A01::/32",
"status": ["active"],
"entities": [
{
"objectClassName": "entity",
"handle": "t9f3a1c2e7b4…",
"roles": ["registrant"],
"vcardArray": ["vcard", [
["version", {}, "text", "4.0"],
["fn", {}, "text", "Whisper agent tenant"]
]]
},
{
"objectClassName": "entity",
"handle": "WHISPER-ABUSE",
"roles": ["abuse"],
"vcardArray": ["vcard", [
["version", {}, "text", "4.0"],
["fn", {}, "text", "Whisper Security abuse desk"],
["email", {}, "text", "abuse@whisper.online"]
]]
}
],
"events": [
{ "eventAction": "registration", "eventDate": "2026-05-11T14:02:03Z" },
{ "eventAction": "last changed", "eventDate": "2026-06-30T09:11:47Z" }
],
"links": [
{ "rel": "self", "href": "https://rdap.whisper.online/ip/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4" },
{ "rel": "up", "href": "https://rdap.db.ripe.net/ip/2a04:2a01::/32" }
],
"notices": [
{ "title": "Source", "description": ["Objects returned come from the Whisper agent registry."] }
]
}
Field by field, this is the whole model:
objectClassName— the discriminator ("ip network","domain","entity"). A generic client dispatches on this one field; it never needs registry-specific logic.handle— the registry's own opaque identifier for the object. For a registrantentityit's a per-tenant handle, never a raw account ID — the same opacity principle as DANE's per-agent TLSA, applied to the registry layer.entities— the parties attached to the object, each tagged with an IANA-registeredrolesvalue (registrant,technical,abuse,administrative). Names/contacts are encoded as jCard (RFC 7095) insidevcardArray— the same vCard model your phone uses, just JSON-shaped.events— a typed timeline (registration,last changed,expiration,transfer) instead of a WHOIS registry's ad-hoc "Creation Date:" line.links— RFC 8288 Web Linking. Theselflink is canonical; arel: "up"link is the referral to the parent registrant of the covering block — this is how an RDAP client walks from a single/128up to the RIPE NCC allocation for2a04:2a01::/32(registered to AS219419), exactly the way a resolver walks up delegation in DNS.status— a controlled vocabulary (active,inactive,pending delete) instead of free text.notices/remarks— structured advisories (ToS pointers, source attribution) that a client can render or ignore, never regex out of a paragraph.
A domain object for an agent's forward name (acef2002a323d40d4.demo.agents.whisper.online) follows the identical shape, with ldhName in place of startAddress/endAddress and (when DNSSEC is configured) a secureDNS block carrying the delegation's DS/key data.
Errors are structured too — a miss returns HTTP 404 with an RFC 9083 error body, never an opaque 500:
{ "errorCode": 404, "title": "Not Found", "description": ["No RDAP object for this target."] }
The referral chain: from IANA down to a /128
RDAP is designed to be walked, not memorized — and you do not need to know Whisper's RDAP server exists to find one of our agents. A general-purpose client starts at IANA's RFC 9224 bootstrap registry, learns that 2a04:2a01::/32 is served by rdap.db.ripe.net, and queries RIPE — which authoritatively names the block WHISPER-AGENTS (viaGraph b.v.), with no Whisper-specific knowledge required. RIPE holds and answers for the whole /32; it does not hand the query down to us, so to reach the individual /128 — the specific agent, its status, its registrant — you then query rdap.whisper.online, whose up link points straight back at the RIPE object. Either way the chain is unbroken and machine-walkable from the IANA root all the way to the exact agent, with no step that requires trusting our prose.
RDAP is one of the seven keyless verification proofs — no account, no SDK, just HTTPS. It composes with DANE (who signs the TLS key) and the transparency log (when that key was issued) to answer "is this really a Whisper agent" without ever trusting our API to tell the truth.
Ownership history
A /128 can be released and re-allocated, so the registry keeps every holder, not just the current one. It's exposed on the IP object itself as two query parameters — ?history for the full array of ownership intervals, ?time=<instant> for the single holder at a moment — so it needs no special client:
# stock tools: every ownership interval, straight off the endpoint
curl -s 'https://rdap.whisper.online/ip/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4?history' | jq '.history'
# stock tools: the holder at a past instant (RFC 3339)
curl -s 'https://rdap.whisper.online/ip/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4?time=2026-05-01T00:00:00Z' | jq '{handle, status, entities}'
# the CLI wraps the same two parameters as flags
whisper rdap 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 --history # every ownership interval
whisper rdap 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 --at 2026-05-01T00:00:00Z # the holder at that instant
Handles on that trail are always opaque per-tenant identifiers — never a raw account ID — the same way dig -x never leaks anything beyond a hostname.
Try it — with stock tools, and with Whisper
With stock tools (no Whisper software — any RDAP-aware client works identically against any registry):
# discover it cold — no knowledge of Whisper's RDAP server needed. A generic client follows
# the IANA bootstrap (RFC 9224) to the RIR that holds the block; rdap.org is one public
# redirector that does the walk for you. It lands on RIPE, which names the space:
curl -sL https://rdap.org/ip/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 | jq '{handle, name, remarks}'
# -> handle "2a04:2a01::/32", name "WHISPER-AGENTS",
# remark "Whisper agent identity space (viaGraph b.v.) - per-/128 assignments"
# then the specific /128 — the individual agent — from Whisper's own RDAP (its `up` link
# points back at that RIPE object, so the chain is complete). RFC 9083 JSON:
curl -s -H 'Accept: application/rdap+json' \
https://rdap.whisper.online/ip/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 | jq '{handle, status, entities, events}'
# the domain object, for the agent's forward name
curl -s https://rdap.whisper.online/domain/acef2002a323d40d4.demo.agents.whisper.online | jq '{ldhName, status, links}'
# the legacy port-43 protocol, RFC 3912, same record
whois -h whois.whisper.online 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4
# the keyless full-chain verdict — no key, no SDK
curl -s https://rdap.whisper.online/verify-identity/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 \
| jq '{is_whisper_agent, dane_ok, jws_ok}'
With Whisper (the CLI wraps the same endpoints, always emits the raw RDAP JSON — it's the data, not a summary):
whisper rdap 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 # IP object
whisper rdap acef2002a323d40d4.demo.agents.whisper.online # domain object
whisper rdap 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 --history # ownership trail
whisper verify --trustless 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 # re-derives RDAP + DANE + the
# transparency log itself, anchored
# at the IANA DNSSEC root — the
# Whisper API is never trusted
Both paths hit the same public server and get the same bytes back; the CLI just saves you the curl/jq boilerplate and adds --trustless re-derivation for when you don't want to trust us to tell you the truth about ourselves. The whisper_rdap tool ships in the MCP server too, so an agent can look up a peer's RDAP record as a native tool call rather than shelling out.
Next
- Verify an agent — all seven keyless proofs, RDAP included
- DANE & DNSSEC — the cryptographic pin that RDAP's referral chain complements