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 break when a registry re-wrapped a line, you 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": "acef2002a323d40d4",
"startAddress": "2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4",
"endAddress": "2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4",
"ipVersion": "v6",
"name": "{name}",
"type": "Whisper agent identity",
"status": ["active"],
"country": "NL",
"entities": [
{ "objectClassName": "entity", "handle": "{tenant}", "roles": ["registrant"] }
],
"events": [
{ "eventAction": "registration", "eventDate": "2026-07-02T16:14:21.482Z" }
],
"links": [
{ "rel": "self", "href": "https://rdap.whisper.online/ip/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4", "type": "application/rdap+json" },
{ "rel": "related", "href": "https://acef2002a323d40d4.{tenant}.agents.whisper.online", "title": "forward (AAAA)" },
{ "rel": "related", "href": "4.d.0.4.d.3.2.3.a.2.0.0.2.f.e.c.4.7.a.c.a.5.b.e.1.0.a.2.4.0.a.2.ip6.arpa", "title": "reverse (PTR)" },
{ "rel": "related", "href": "https://rdap.whisper.online/domain/acef2002a323d40d4.{tenant}.agents.whisper.online", "type": "application/rdap+json", "title": "domain (RDAP)" },
{ "rel": "related", "href": "https://rdap.db.ripe.net/ip/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4", "title": "RIR (RIPE) aggregate 2a04:2a01::/32", "type": "application/rdap+json" },
{ "rel": "rdap-up", "href": "https://rdap.db.ripe.net/ip/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4", "title": "RIR (RIPE) aggregate 2a04:2a01::/32", "type": "application/rdap+json" },
{ "rel": "related", "href": "https://rdap.whisper.online/ip/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4", "type": "application/rdap+json", "title": "identity bundle (whisper-id1)" }
],
"notices": [
{ "title": "Whisper RDAP", "description": ["Whisper agent-identity RDAP service. Base URL: https://rdap.whisper.online/"], "links": [{ "rel": "help", "href": "https://rdap.whisper.online/" }] },
{ "title": "Whisper NS", "description": ["Served by Whisper NS (ns1).", "Whisper agent-identity registry. RDAP is canonical (https://rdap.whisper.online)."] }
],
"remarks": [
{ "description": ["Whisper agent identity. The IPv6 /128 is the identity anchor: reverse PTR resolves to acef2002a323d40d4.{tenant}.agents.whisper.online. Assigned from 2a04:2a01::/32 (AS219419, viaGraph B.V.). Connectivity posture: tier1.5."] }
]
}
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. It's the same lowercase agent handle Identity derives from the address - never a raw account ID - the same opacity principle as DANE's per-agent TLSA, applied to the registry layer. There is noparentHandlefield: the parent allocation is reached throughlinks, below, not a bare pointer.entities- the parties attached to the object, each tagged with an IANA-registeredrolesvalue; a Whisper/128carries one bareregistrantentity keyed by the opaque tenant handle. NovcardArray(jCard, RFC 7095) is published for it today - there's no name/contact detail on file to encode, so the field is simply absent rather than an empty placeholder.events- a typed timeline (registration,last changed,expiration,transfer) instead of a WHOIS registry's ad-hoc "Creation Date:" line. Today's record carries the oneregistrationevent Whisper always knows.links- RFC 8288 Web Linking, and the richest part of the object:selfis canonical,relatedentries cross-link the forward AAAA, the reverse PTR, and the siblingdomainobject, andrel: "rdap-up"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 (service description, source attribution, a plain-English summary of what the object is) that a client can render or ignore, never regex out of a paragraph.
A domain object for an agent's forward name (acef2002a323d40d4.t7af9242c44554e0f8e183ac8e6fbf645.agents.whisper.online) follows the identical shape, with ldhName in place of startAddress/endAddress.
Errors are structured too - an address outside Whisper's allocations returns HTTP 404 with an RFC 9083 error body, never an opaque 500:
curl -s https://rdap.whisper.online/ip/2001:db8::1
{ "rdapConformance": ["rdap_level_0"], "errorCode": 404, "title": "Not Found", "description": ["no Whisper agent identity for this address"] }
An address inside one of our allocations that has no specific assignment is a different question, and gets a different answer. RDAP's IP lookup is a covering lookup: you return the most specific object that contains the address. We hold 2a04:2a01::/32 and 2a04:2a00::/32, so for any address in them there is something true to say, and a 404 would assert "nothing here" when the truth is "no assignment, and here is the block it sits in". RIPE answers the same way for the same addresses, so a client walking down the referral chain from the RIR is never handed less than it started with.
curl -s https://rdap.whisper.online/ip/2a04:2a01:dead:beef::1
{
"rdapConformance": ["rdap_level_0"],
"objectClassName": "ip network",
"handle": "2a04:2a01::/32",
"startAddress": "2a04:2a01::",
"endAddress": "2a04:2a01:ffff:ffff:ffff:ffff:ffff:ffff",
"ipVersion": "v6",
"name": "WHISPER-AGENTS",
"type": "Whisper allocation",
"status": ["active"],
"country": "NL",
"remarks": [{ "description": ["This object describes the covering allocation 2a04:2a01::/32 ... It is an allocation record, not an assignment ..."] }]
}
The shape is the tell, and it is deliberate. handle, startAddress and endAddress are the block's, not the address you asked about. There is no ldhName, no registrant entity and no registration event, because none of those exist for an unassigned address. So this object can never be mistaken for an identity, and when an identity is released this is what its address returns: proof of absence rather than an ambiguous 404.
Reverse DNS behaves differently on purpose. dig -x on an unassigned address stays NXDOMAIN, because "this address has no PTR" is the check we tell people to run, and synthesising PTRs across the block would break it. RDAP answers the covering question; DNS answers the assignment question; making them agree would ruin the second one.
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 rdap-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.
Point-in-time, proof-backed
The ?history and ?time= holder history above is Whisper's own record for our own address space: useful, but nothing you can verify beyond trusting our answer. For a proof-backed version of the same idea, an RDAP answer with a receipt that folds to the same signed checkpoint as every other proof on this site, and one that also works for subjects beyond our own /128s, see Point-in-time RDAP.
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 `rdap-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.t7af9242c44554e0f8e183ac8e6fbf645.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.t7af9242c44554e0f8e183ac8e6fbf645.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