Skip to main content

RDAP API Python SDK

PyPI version Python versions License: MIT

Official Python SDK for the RDAP API — look up domains, IP addresses, ASNs, nameservers, and entities over RDAP, with a WHOIS fallback for the TLDs RDAP does not cover.

Installation

pip install rdapapi

Quick start

from rdapapi import RdapApi

api = RdapApi("your-api-key")

# Domain lookup
domain = api.domain("google.com")
print(domain.registrar.name)  # "MarkMonitor Inc."
print(domain.dates.expires)  # "2028-09-14T04:00:00Z"
print(domain.nameservers)  # ["ns1.google.com", ...]
print(domain.dnssec)  # False, or None where the registry publishes no status
print(domain.meta.source)  # "rdap", or "whois" for a TLD with no RDAP server

# IP address lookup — pass an address or a CIDR block
ip = api.ip("8.8.8.8")
print(ip.name)  # "GOGL"
print(ip.cidr)  # ["8.8.8.0/24"]
print(ip.geofeed)  # RFC 8805 geofeed URL, or None

# ASN lookup
asn = api.asn(15169)
print(asn.name)  # "GOOGLE"

# Nameserver lookup
ns = api.nameserver("ns1.google.com")
print(ns.ip_addresses.v4)  # ["216.239.32.10"]

# Entity lookup
entity = api.entity("GOGL")
print(entity.name)  # "Google LLC"
print(entity.autnums[0].handle)  # "AS15169"

api.close()

Bulk domain lookups

Look up multiple domains in a single request (Pro and Business plans). Up to 10 domains per call, with concurrent upstream fetches:

result = api.bulk_domains(["google.com", "github.com", "invalid..com"], follow=True)

print(result.summary)  # total=3, successful=2, failed=1

for r in result.results:
    if r.status == "success":
        print(f"{r.data.domain}: {r.data.registrar.name}")
    else:
        print(f"{r.domain}: {r.error}")

follow and whois apply to every domain in the request. Each domain counts as one request toward your monthly quota. Starter plans receive a PlanUpgradeRequiredError (403), a subclass of SubscriptionRequiredError.

Registrar follow-through

For thin registries like .com and .net, the registry only returns basic registrar info. Use follow=True to follow the registrar's RDAP link and get richer contact data:

domain = api.domain("google.com", follow=True)
print(domain.entities.registrant.organization)  # "Google LLC"
print(domain.entities.registrant.email)  # "registrant@google.com"

WHOIS fallback

For the ccTLDs IANA lists no RDAP server for, the answer is read from the registry's WHOIS server and returned in the same shape. meta.source says which protocol answered, and meta.server names the host:

domain = api.domain("google.it")
print(domain.meta.source)  # "whois"
print(domain.meta.server)  # "whois.nic.it"

A WHOIS registry publishes fewer fields — several give no dates, some no registrar — and anything it withholds is None rather than inferred. meta.rdap_server and meta.raw_rdap_url are absent, since WHOIS has no URL form.

Pass whois=False to refuse the fallback, so such a TLD raises NotSupportedError instead:

domain = api.domain("google.it", whois=False)  # raises NotSupportedError

Declared redactions

redacted carries what the upstream server declared it withheld, mirroring the shape of the record, so a claim about entities.registrant.name sits at redacted.entities["registrant"]["name"]. It is None when the server declared nothing, which is not evidence that nothing was withheld:

domain = api.domain("google.co.uk")

if domain.redacted is not None:
    for role, fields in domain.redacted.entities.items():
        for field, method in fields.items():
            print(f"{role}.{field} withheld by {method}")
    # registrant.email withheld by replacementValue

The method is one of removal, emptyValue, partialValue or replacementValue. A server may send something else, which is passed through unchanged, so it stays a plain str.

Health check

print(api.ping().status)  # "ok"

Costs no quota. The endpoint itself answers unauthenticated callers, but the SDK is an authenticated client: RdapApi rejects an empty key, so ping() checks that the API answers you.

Error handling

from rdapapi import (
    RdapApi,
    AuthenticationError,
    NotFoundError,
    NotSupportedError,
    RateLimitError,
    SubscriptionRequiredError,
)

api = RdapApi("your-api-key")

try:
    domain = api.domain("example.nope")
except NotSupportedError:
    print("The TLD is not covered by RDAP.")
except NotFoundError:
    print("The domain is not registered.")
except RateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after}s")
except AuthenticationError:
    print("Invalid API key")
except SubscriptionRequiredError as e:
    if e.error == "forbidden":
        print("This IP is temporarily blocked. Retry later.")
    else:
        print("Subscribe at https://rdapapi.io/pricing")

NotSupportedError is a subclass of NotFoundError, and PlanUpgradeRequiredError of SubscriptionRequiredError, so catching the parent still handles both cases. All exceptions inherit from RdapApiError and include status_code, error, message and retry_after attributes. Branch on error, the machine-readable code — never on message, and never on the endpoint: any code can answer any endpoint.

Exception HTTP Status error When
ValidationError 400 invalid_domain, invalid_ip, invalid_asn, invalid_nameserver, invalid_handle, invalid_prefix, invalid_since, bad_request Invalid input format
AuthenticationError 401 unauthenticated Missing or invalid API key
SubscriptionRequiredError 403 subscription_required No active subscription
SubscriptionRequiredError 403 forbidden This IP is temporarily blocked. It lifts on its own, so retry later — API rate limits never cause it
PlanUpgradeRequiredError 403 plan_upgrade_required Endpoint needs a higher plan
NotFoundError 404 not_found Namespace is covered but no record exists
NotSupportedError 404 not_supported Namespace (TLD, IP range, ASN range) is covered by neither RDAP nor the WHOIS fallback
MethodNotAllowedError 405 method_not_allowed Wrong HTTP method for the endpoint
PayloadTooLargeError 413 payload_too_large Request body too large
RequestFailedError 422 request_failed Body failed validation; errors names the fields
RateLimitError 429 rate_limit_exceeded, quota_exceeded, too_many_requests Rate limit or quota exceeded
UpstreamError 502 lookup_failed, bad_gateway Upstream RDAP server error
TemporarilyUnavailableError 503 temporarily_unavailable, service_unavailable Data temporarily unavailable
GatewayTimeoutError 504 gateway_timeout Request did not complete in time
ServerError 5xx server_error Any other server-side failure

UpstreamError, TemporarilyUnavailableError and GatewayTimeoutError all inherit from ServerError, so except ServerError covers the 5xx failures worth retrying after a delay. An error body that is not JSON — a CDN edge page, say — surfaces the same way, with error set to "unknown_error".

One 403 is retryable and does not go through ServerError: forbidden is a temporary IP block that lifts on its own. It shares SubscriptionRequiredError with subscription_required, which is not retryable at all, so branch on e.error there rather than showing every 403 a billing prompt.

retry_after is the number of seconds to wait. The Retry-After header wins, in either form RFC 9110 allows — delta-seconds, or an HTTP-date, which arrives whenever an upstream registry's own header is passed through — and the body's retry_after is the fallback. It is None when neither gave an estimate.

A rejected request body names the fields it rejected:

from rdapapi import RequestFailedError

try:
    api.bulk_domains(["a.com"] * 11)
except RequestFailedError as e:
    print(e.errors)  # {"domains": ["The domains field must not have more than 10 items."]}

Supported TLDs catalog

List every TLD the API can resolve, with the date support was added and a qualitative summary of which fields the registry's RDAP server populates. Does not count against your monthly quota.

tlds = api.tlds()
print(f"{tlds.meta.count} TLDs, coverage {tlds.meta.coverage:.0%}")

for tld in tlds.data:
    print(f"{tld.tld}: {tld.protocol} via {tld.server}")
    availability = tld.field_availability
    if availability is not None:
        print(f"  expires_at={availability.expires_at}")

protocol is "whois" for the ccTLDs IANA lists no RDAP server for. Those entries have no rdap_server_host and no rdap_server_url — use server for the host that answers, whichever protocol it speaks — and no field_availability, which is measured from RDAP responses only.

Filter to recent additions or to a single registry:

recent = api.tlds(since="2026-04-01T00:00:00Z")
verisign = api.tlds(server="rdap.verisign.com")

Pass back the previous etag to skip the transfer when nothing has changed:

first = api.tlds()
later = api.tlds(if_none_match=first.etag)
if later is None:
    print("No change since last poll")

Look up a single TLD:

com = api.tld("com")
print(com.data.server)  # "rdap.verisign.com"

Async support

import asyncio
from rdapapi import AsyncRdapApi


async def main():
    async with AsyncRdapApi("your-api-key") as api:
        domain, ip, asn = await asyncio.gather(
            api.domain("google.com"),
            api.ip("8.8.8.8"),
            api.asn(15169),
        )
        print(f"{domain.domain}: {domain.registrar.name}")


asyncio.run(main())

Serialization

All response objects are Pydantic models with full type hints:

domain = api.domain("google.com")

# Convert to dict
data = domain.model_dump()

# Convert to JSON string
json_str = domain.model_dump_json()

Configuration

api = RdapApi(
    "your-api-key",
    base_url="https://rdapapi.io/api/v1",  # default
    timeout=30,  # seconds, default
)

Development

Set up pre-commit hooks (runs lint + tests before each commit):

git config core.hooksPath .githooks

License

MIT

Metadata

Release files for rdapapi 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rdapapi 0.6.0
File Size Uploaded
rdapapi-0.6.0.tar.gz 27.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for rdapapi 0.6.0
File Interpreter ABI Platform
rdapapi-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 43.6 kB

Release files / rdapapi-0.6.0.tar.gz

Download URL rdapapi-0.6.0.tar.gz
Size 27.6 kB
Tags Source
SHA-256 checksum
How to use checksums
c8fb100abca6ca3513d5ef3015de90a73f52914c069a7db81b7e4594896a8685
BLAKE2b-256 checksum
How to use checksums
484206012f2682f59762ce491d6a2c1822340a329ff16b5f6e8e69ad96712677
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release files / rdapapi-0.6.0-py3-none-any.whl

Download URL rdapapi-0.6.0-py3-none-any.whl
Size 16.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5ef2d981d5442a2d48be402249e6f317be420d86a2a19ed13f0d13322f82c4ea
BLAKE2b-256 checksum
How to use checksums
a1abe1a9454fb2e31aa7bcf1ecb5e00c86cafb6b1bf4dbbe2adbbb78dca3fec6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page