How to Look Up IP Ownership via RDAP
March 2, 2026
An IP address belongs to a registered network block. That block belongs to an organization. RDAP is the protocol that maps an IP to its owner, returning structured JSON with the organization, the CIDR range, and the abuse contact.
How IP allocation works
IP addresses are allocated in blocks (CIDR ranges) to organizations by one of five Regional Internet Registries (ARIN, RIPE NCC, APNIC, LACNIC, AFRINIC). There are no per-IP records in RDAP. When you look up an IP, you get the most specific registered block that contains it, not a record for the individual address.
Querying RDAP directly
RDAP uses IANA bootstrap registries to route queries to the right registry. The bootstrap files at https://data.iana.org/rdap/ipv4.json and https://data.iana.org/rdap/ipv6.json map IP prefixes to registry base URLs.
To query ARIN directly for a US IP:
curl https://rdap.arin.net/registry/ip/8.8.8.8
The raw response looks like this (abbreviated):
{
"objectClassName": "ip network",
"handle": "NET-8-8-8-0-2",
"startAddress": "8.8.8.0",
"entities": [
{
"objectClassName": "entity",
"handle": "GOGL",
"roles": ["registrant"],
"vcardArray": [
"vcard",
[
["version", {}, "text", "4.0"],
["fn", {}, "text", "Google LLC"],
["adr", {"label": "1600 Amphitheatre Pkwy"}, "text",
["", "", "", "", "", "", "US"]]
]
]
}
]
}
Every contact is encoded as a vcardArray (RFC 6350). Extracting an organization name means indexing into nested arrays — find the entry where the first element is "fn", then read the fourth element. Abuse contacts appear as separate entities in the same list, distinguished by "roles": ["abuse"]; finding the email means first locating the right entity, then scanning its vcardArray for the "email" entry. There is no top-level organization or abuse_email field.
Each registry also enforces its own rate limits. ARIN, RIPE, and APNIC have different thresholds and different behavior when you exceed them. If you query multiple IPs in a loop, you will hit these.
The normalized IP response
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://rdapapi.io/api/v1/ip/1.1.1.1
{
"handle": "1.1.1.0 - 1.1.1.255",
"name": "APNIC-LABS",
"type": "ASSIGNED PORTABLE",
"start_address": "1.1.1.0",
"end_address": "1.1.1.255",
"ip_version": "v4",
"parent_handle": null,
"country": "AU",
"status": ["active"],
"cidr": ["1.1.1.0/24"],
"dates": {
"registered": "2011-08-10T23:12:35Z",
"updated": "2023-04-26T22:57:58Z"
},
"entities": {
"registrant": {
"handle": "ORG-ARAD1-AP",
"name": "APNIC Research and Development",
"organization": null,
"email": "[email protected]",
"phone": "+61-7-38583100",
"address": "6 Cordelia St\nSouth Brisbane QLD 4101\nAustralia",
"contact_url": null,
"country_code": null
},
"abuse": {
"handle": "IRT-APNICRANDNET-AU",
"name": "IRT-APNICRANDNET-AU",
"organization": null,
"email": "[email protected]",
"phone": null,
"address": "PO Box 3646\nSouth Brisbane, QLD 4101\nAustralia",
"contact_url": null,
"country_code": null
}
},
"remarks": [
{
"title": "description",
"description": "APNIC and Cloudflare DNS Resolver project\nRouted globally by AS13335/Cloudflare\nResearch prefix for APNIC Labs"
}
],
"port43": "whois.apnic.net"
}
Two things in this response are worth pausing on. country: "AU" for a globally distributed DNS resolver — because country reflects the registration record, not where traffic comes from. And the registrant is APNIC Research, not Cloudflare — because APNIC holds the IP allocation; Cloudflare operates the service under a partnership agreement. Both of these are covered in what RDAP won't tell you.
The key fields:
name: the network name assigned by the registry, such asAPNIC-LABSorGOGL. A short label; the full org name is inentities.registrant.name.type: the allocation category.DIRECT ALLOCATIONmeans the RIR assigned the block directly to this org.ASSIGNED PORTABLEmeans the block is assigned for a specific use and can move between networks.REALLOCATIONorREASSIGNMENTmeans a larger block holder (typically an ISP) sub-allocated this range —entities.registrantmay be a downstream customer, not the top-level allocatee.cidr: the network block this IP belongs to. An IP can fall inside multiple overlapping allocations; RDAP returns the most specific match.country: from the registration record, not from where traffic originates. Not all registries include it — ARIN records sometimes omit it.status: typically["active"]. ARIN records for legacy pre-policy space may show["legacy"].entities.registrant.name: who holds the allocation. Note thatorganizationis a separate field and is often null for IP records — the org name is inname.dates.registered/dates.updated: when the block was first allocated and when the record was last modified. A recent registration date can be a useful signal in threat intelligence workflows.entities.abuse: where to send abuse reports. Not always present. See the abuse reporting section below.parent_handle: the larger allocation this block was sub-divided from. Useful for tracing delegation chains (e.g., a /24 carved out of a /8).
IPv6
IPv6 uses the same query format and returns the same response structure, with ip_version: "v6" and a longer CIDR notation. Two things worth knowing: IPv6 blocks are much larger (a /32 covers over four billion /64 subnets), and RDAP coverage is more variable across registries. Some RIRs have less complete data for IPv6 than for IPv4.
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://rdapapi.io/api/v1/ip/2606:4700:4700::1111
CIDR lookups
If you have a prefix from a threat feed, firewall rule, or routing table dump, pass it directly:
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://rdapapi.io/api/v1/ip/8.8.8.0/24
If the prefix does not exactly match a registered block, the API returns the smallest registered block that contains it. For example, querying 192.0.2.0/24 when only 192.0.0.0/21 is registered returns the /21.
Private ranges (RFC 1918: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) are not registered in any RIR and will return 404.
Code examples
import os
from rdapapi import RdapApi, NotFoundError
api = RdapApi(os.environ["RDAP_API_KEY"])
ips = ["8.8.8.8", "1.1.1.1", "104.21.0.0"]
for addr in ips:
try:
ip = api.ip(addr)
org = ip.entities.registrant.name if ip.entities.registrant else "unknown"
abuse = ip.entities.abuse.email if ip.entities.abuse else "none"
print(f"{addr}: {org} ({ip.country}), abuse: {abuse}")
except NotFoundError:
print(f"{addr}: not found")
# 8.8.8.8: Google LLC (None), abuse: none # ARIN omits country; no abuse contact registered
# 1.1.1.1: APNIC Research and Development (AU), abuse: [email protected] # AU = APNIC registration
# 104.21.0.0: Cloudflare, Inc. (None), abuse: [email protected]
import { RdapApi } from "rdapapi";
const api = new RdapApi(process.env.RDAP_API_KEY);
const ip = await api.ip("8.8.8.8");
console.log(ip.name); // GOGL
console.log(ip.cidr); // ["8.8.8.0/24"]
console.log(ip.country); // null — ARIN records sometimes omit country
console.log(ip.entities?.registrant?.name); // Google LLC
console.log(ip.entities?.abuse?.email); // undefined — no abuse contact for this block
export TOKEN="your-api-key"
# Get the organization name for an IP
curl -s -H "Authorization: Bearer $TOKEN" \
https://rdapapi.io/api/v1/ip/8.8.8.8 \
| jq '.entities.registrant.name'
# "Google LLC"
# Get the abuse email
curl -s -H "Authorization: Bearer $TOKEN" \
https://rdapapi.io/api/v1/ip/1.1.1.1 \
| jq '.entities.abuse.email'
# "[email protected]"
Practical use cases
Abuse reporting
entities.abuse.email is not always present. RIPE records usually include it. ARIN and APNIC coverage is less consistent. When it is missing, fall back to logging for manual review:
ip = api.ip(suspicious_ip)
if ip.entities.abuse and ip.entities.abuse.email:
send_abuse_report(ip.entities.abuse.email, report_body)
elif ip.entities.registrant:
org = ip.entities.registrant.name or "unknown"
log.warning(f"No abuse contact for {suspicious_ip} ({org}). Manual review needed.")
else:
log.warning(f"No contact data for {suspicious_ip}")
For cloud IPs, the abuse contact routes to the cloud provider, not the customer behind the VM. Major providers (AWS, Google, Cloudflare) have dedicated abuse teams and usually acknowledge reports within a few days, but action against the customer is at their discretion.
Enriching security alerts
Add network context to your SIEM or alerting pipeline. When an alert fires on an IP, annotate it with org name, country, and network block:
ip = api.ip(alert.source_ip)
registrant = ip.entities.registrant
alert.enrich({
"org": registrant.name if registrant else "Unknown",
"country": ip.country,
"network": ip.cidr[0] if ip.cidr else None,
})
Identifying cloud and CDN traffic
RDAP is not the right tool for this. The same organization can appear under different names across registries (Amazon's ARIN registrant name differs from their RIPE NCC entries), and substring matching on those names is fragile in production.
The reliable approach is cloud provider CIDR lists, which the major providers publish and keep current:
- AWS:
https://ip-ranges.amazonaws.com/ip-ranges.json - Google Cloud:
https://www.gstatic.com/ipranges/cloud.json - Cloudflare:
https://www.cloudflare.com/ips-v4
Here is a minimal example using the AWS list (IPv4 only; for IPv6, use data["ipv6_prefixes"] and the ipv6_prefix key):
import ipaddress
import requests
def load_aws_cidrs():
data = requests.get("https://ip-ranges.amazonaws.com/ip-ranges.json").json()
return [ipaddress.ip_network(p["ip_prefix"]) for p in data["prefixes"]]
aws_cidrs = load_aws_cidrs()
addr = ipaddress.ip_address("54.239.28.85")
is_aws = any(addr in net for net in aws_cidrs)
Use RDAP for ownership lookups and abuse contacts. Use provider CIDR lists for cloud and CDN detection.
What RDAP won't tell you
RDAP gives you the allocation owner: the organization that received the IP block from a registry. It does not give you:
- The end customer. For cloud IPs,
entities.registrant.nameis the cloud provider (AWS, Google, Cloudflare), not the tenant behind the VM. There is no protocol-level way to trace who rented a specific IP. - Geolocation. The
countryfield comes from the registration record, not from where traffic actually originates. For city-level location, use a dedicated geo-IP database (MaxMind, ip-api). - Reputation or threat intelligence. RDAP is a registration database. Threat scores and blocklist membership are separate (VirusTotal, AbuseIPDB).
- PTR records. Reverse DNS is separate from RDAP. Query it with
dig -x <IP>or via your DNS library.
An IP registered to AWS tells you nothing about the application running on it.
Try it
Look up any IP for free. No account needed for the web interface. For programmatic access, get an API key. The full IP response schema documents every optional field, including less common ones like port43 and remarks.