# 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 `/128`s 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"`:

```json
{
  "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](/docs/identity) derives from the address - never a raw account ID - the same opacity principle as [DANE's per-agent TLSA](/docs/dane), applied to the registry layer. There is no `parentHandle` field: the parent allocation is reached through `links`, below, not a bare pointer.
- **`entities`** - the parties attached to the object, each tagged with an IANA-registered `roles` value; a Whisper `/128` carries one bare `registrant` entity keyed by the opaque tenant handle. No `vcardArray` (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 one `registration` event Whisper always knows.
- **`links`** - RFC 8288 Web Linking, and the richest part of the object: `self` is canonical, `related` entries cross-link the forward AAAA, the reverse PTR, and the sibling `domain` object, and the way **up** crosses from our service to the registry's: `rel: "rdap-related-up"` (draft-ietf-regext-rdap-referrals) points at the RIPE NCC's own object for the covering block, `https://rdap.db.ripe.net/ip/2a04:2a01::/32` (registered to **AS219419**), and `rel: "rdap-base-up"` (draft-ranjbar-regext-rdap-subordinate-referrals) names the RIPE NCC's RDAP service itself. The target is the covering block's lookup, never the `/128`'s, so a client walking up never lands back here, exactly the way a resolver walks up delegation in DNS. Every link carries `value`, the URL of the object it sits in (RFC 9083 section 4.2), and each per-address object opens with a "Published by the holder" notice saying whose data it is.
- **`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:

```sh
curl -s https://rdap.whisper.online/ip/2001:db8::1
```

```json
{ "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.

```sh
curl -s https://rdap.whisper.online/ip/2a04:2a01:dead:beef::1
```

```json
{
  "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-related-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](/docs/verify) - no account, no SDK, just HTTPS. It composes with [DANE](/docs/dane) (who signs the TLS key) and the [transparency log](/docs/transparency) (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:

```bash
# 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`](/docs/verify) 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 `/128`s, see [Point-in-time RDAP](/docs/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):

```bash
# 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-related-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):

```bash
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](/docs/cli) 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](/docs/verify) - all seven keyless proofs, RDAP included
- [DANE & DNSSEC](/docs/dane) - the cryptographic pin that RDAP's referral chain complements
