# Bulk WHOIS & RDAP Domain Lookup: DNS, SSL (`automation_craft/domain-whois-rdap-lookup`) Actor

Look up thousands of domains over RDAP, the protocol that replaced WHOIS: registrar, IANA id, creation and expiry dates, EPP status codes, nameservers, DNSSEC and availability across 1,200 TLDs. Adds DNS records and SSL certificate details, flags redacted fields, exports to CSV or JSON.

- **URL**: https://apify.com/automation\_craft/domain-whois-rdap-lookup.md
- **Developed by:** [Automation Craft](https://apify.com/automation_craft) (community)
- **Categories:** Developer tools, Lead generation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 domain records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### Bulk WHOIS & RDAP Domain Lookup: DNS, SSL

A **bulk WHOIS lookup** that reads registration data straight from the registries over RDAP, the protocol ICANN made the definitive source for gTLD registration data in January 2025. Give it a list of domains, get one row per domain with registrar, creation and expiry dates, status codes, nameservers, DNSSEC, availability, live DNS records and the SSL certificate the site serves. No API key, no login, no per-domain rate limit to manage yourself.

It covers **1,200 top level domains** from the official IANA bootstrap file, and it tells you honestly which fields the registry withholds instead of handing you a row full of silent nulls.

### Quick start

1. Paste your domains into **Domains**, or point **Domain list URL** at a text or CSV file with one domain per line. Full URLs, subdomains, email addresses and internationalized names are all accepted and reduced to the registered domain.
2. Pick your **Checks**. RDAP is always on. DNS and SSL cost nothing extra.
3. Set **Maximum charged domain records** so a large list cannot surprise you.
4. Click **Start**, then download the results as JSON, CSV or Excel, or read them from the API.

A run of 5 domains finishes in about 20 seconds. A run of 1,000 domains across mixed TLDs takes a few minutes.

### What you get

One record per domain. Fill rates below were measured on 176 live records across 49 TLDs during the build, and they are what you should expect, not a best case.

| Field | What it is | Fill rate |
|---|---|---|
| `domain`, `tld` | The registered domain, lower case, punycode for IDNs | 100% |
| `isRegistered` | `true`, `false` (the registry says the name does not exist) or `null` (nobody answered, and the row is free) | 100% |
| `statusReason` | Why a domain is reported available, or why no registry answered | as needed |
| `rdapServer` | The registry RDAP host that answered | 100% |
| `registrar` | Sponsoring registrar name | 98.3% |
| `registrarIanaId` | ICANN accreditation number | 96.0% |
| `registrarAbuseEmail` | Registrar abuse contact | 96.6% |
| `createdAt` | Registration date, ISO 8601 | 97.7% |
| `expiresAt`, `daysToExpiry` | Registry expiry date and days remaining | 97.2% |
| `updatedAt` | Last change to the registration record | 100% |
| `statuses` | EPP status codes, for example `client transfer prohibited` | 100% |
| `nameservers` | Delegated nameservers with glue addresses | 100% |
| `dnssec` | Whether the delegation is signed, plus DS and key counts | 98.3% |
| `entities.registrant` | Registrant contact, with a `redacted` flag and the list of withheld fields | present 9.7%, **name 4.5%** |
| `entities.admin`, `entities.tech` | Admin and tech contacts, same redaction flags | 5.7% and 6.3% |
| `dns` | Live A, AAAA, MX, NS, TXT, CAA and SOA answers over HTTPS | 100% of registered domains |
| `tls` | Certificate issuer, subject, validity window, days to expiry, SAN count | 100% of hosts that answer on port 443 |
| `whois` | Port 43 fallback details, for TLDs with no RDAP service | only where used |
| `billable` | Whether this row is charged. A registry answered definitively AND no earlier entry in the run already produced the same registration | 100% |
| `registrationKey` | The registration this row is billed against, so you can see which entries share one | 100% of billable rows |

#### Why registrant names are almost always empty

They are redacted at the registry under GDPR and ICANN's temporary specification, not missing from this Actor. In the measurement above a registrant name field carried a value in 8 of 176 records and in 0 of 104 `.com` records, and three of those 8 were placeholders (`REDACTED FOR PRIVACY`, `Registration Private`, `Domain Administrator`) rather than a real party. The genuine names came from `.ca`, `.fr`, `.nl`, `.ai` and `.art`. Where a contact is present but withheld, it carries `redacted: true` and a `redactedFields` list, so you can tell "the registry withholds this" apart from "this domain has no such contact", which is a plain `null`.

### How much does it cost to look up a domain?

Pay per event, and only for an answer.

| Event | Price | When it is charged |
|---|---|---|
| **Domain record** | **$0.001** per record (FREE and Bronze), $0.0009 Silver, $0.0008 Gold and above | A registry answered definitively about that exact domain: a full registration record, or a confirmed availability |
| **Actor start** | $0.00005 per run, per gigabyte of run memory | Apify's standard start fee. The Actor runs on 256 MB |

That is **$1.00 per 1,000 domains** on the Free and Bronze plans, and **$0.80 per 1,000** on Gold and above. DNS records and SSL certificate details are included in that price, not billed separately.

What is free:

- rows where every registry path failed (`isRegistered: null`), with the reason on the row,
- domains removed by the TLD filter,
- repeated domains inside one run, and domains a named memory has already delivered,
- unusable input, which produces a status row and no charges,
- the run summary row.

Worked example: 1,000 domains with RDAP and DNS on a Bronze plan costs $1.00 plus one $0.00005 start event, so $1.00005. The same list with the registrant contacts redacted costs exactly the same, because you are charged for the registry's answer, not for the fields it chose to publish.

### Input

| Option | What it does |
|---|---|
| **Domains** | The list to look up. URLs, subdomains, emails and IDNs are all reduced to the registered domain |
| **Domain list URL** | A public text or CSV file, one domain per line, merged with the list above |
| **Only these TLDs** | Keep only `com`, `io` and so on. Filtered domains are skipped free of charge |
| **Checks to run** | `rdap` (always on), `dns`, `tls`, `whois` |
| **DNS record types** | A, AAAA, MX, NS, TXT, CAA, SOA |
| **Include the raw registry response** | Adds the RDAP JSON the registry returned, and the raw WHOIS text under `whois.raw`, to each row the registry answered |
| **Maximum charged domain records** | Hard cap on billed rows |
| **Parallel domains** | 1 to 10. Per-registry pacing is automatic regardless |
| **Cross-run memory name** | Skip domains already answered under that name, free of charge. Domains no registry could answer for are not remembered |
| **Proxy configuration** | Optional and off by default. Registries answer Apify directly |

### How the lookup works

1. The TLD is resolved to its authoritative RDAP server through the **IANA bootstrap file**, which covers 1,200 TLDs, plus a built-in table for popular ccTLDs the bootstrap omits (`io`, `sh`, `ac`, `me`, `us`, `de`, `ch`, `li`) and for the busiest gTLDs, so a run still works if IANA is briefly unreachable.
2. The registry is asked for the domain. A 200 is accepted only when the record it returns is a record for that exact name; anything else is reported as unknown and is free. HTTP 404 from the authoritative server for that TLD means the name is not registered.
3. If the TLD publishes no RDAP service, or the registry's RDAP server did not answer, and you enabled the WHOIS check, the Actor asks IANA for that TLD's WHOIS server and queries it on port 43, then reads what it can from free-form WHOIS text. A truncated or timed-out WHOIS reply is never classified: the row stays unknown and free.
4. DNS records are read over HTTPS from Cloudflare, with Google as the fallback resolver, for the name that was looked up. They are skipped when the registry confirmed the name is not registered. A resolver that answers SERVFAIL or REFUSED is not treated as an answer.
5. The certificate is read from the TLS handshake on port 443 for that same name, on the same condition. Nothing is downloaded from the site.
6. Requests are paced per registry host, so one busy TLD in your list does not slow down or throttle the rest. A rate-limited response is retried with backoff, and a domain that still cannot be answered is returned free with the reason on the row.

### What this Actor does NOT do

- **It does not un-redact registrant details.** Where a registry withholds a contact field, it is flagged, not recovered. No paid WHOIS reseller data is used.
- **It does not bill the same registration twice.** If your list contains `example.com` and
  `www.example.com`, both entries come back with their own row and only the first is charged; the second says
  so in `statusReason` and carries `billable: false`.
- **It does not check subdomains separately.** Every entry is reduced to the registered domain, because RDAP has no object for `www.example.com`. If that name turns out not to exist, up to two longer forms of what you typed are tried before anything is reported. DNS and SSL are then read for the name the registry confirmed, not for the subdomain you typed, and two entries that resolve to one registration produce one row and one charge.
- **It does not query every TLD over RDAP.** Some ccTLDs, `.jp`, `.it`, `.ru`, `.es` and `.cn` among them, publish no public RDAP service; they are answered through the port 43 WHOIS fallback, which returns fewer normalized fields. `.de`, `.ch` and `.li` do publish RDAP, but by registry policy their records are thin: status, nameservers and DNSSEC, with no registrar or dates.
- **It does not tell you the price of an available domain**, and it is not a registrar. A 404 means the registry has no record; registry-reserved and premium names can answer 404 too.
- **It does not resolve IP addresses or ASNs**, and it does not scan ports.
- **It does not know private namespaces** such as `uk.com`, `eu.com` or `za.com`, which are sold by a private
  registry that the TLD's own RDAP service knows nothing about. A name under one of those is reported for the
  parent registration rather than guessed at, and it is never reported as available on the strength of the
  TLD registry's 404.
- **It follows a hostname up to four labels deeper than the registered name.** An entry deeper than that is
  reported as unknown, free of charge, rather than answered for a name the caller did not ask about.
- **It does not follow the registrar's own RDAP referral** for thicker registrar-side data. Registry data only in this version.
- **It does not monitor by itself.** Use an Apify schedule, and a memory name if you want only what is new.

### FAQ

#### Is WHOIS being replaced by RDAP?

Yes. ICANN announced on 27 January 2025 that RDAP is the definitive source of gTLD registration data and that WHOIS is being sunsetted. This Actor reads RDAP first for exactly that reason, and keeps port 43 WHOIS only as a fallback for TLDs that publish no RDAP service.

#### Why is WHOIS data redacted, and what can I still get?

Registrant, admin and tech contacts are withheld from public registration data under GDPR and ICANN policy. Everything else is still public and this Actor returns it: registrar, IANA id, registrar abuse contact, creation, expiry and last-changed dates, EPP status codes, nameservers and DNSSEC. In our measurement a registrant name was present in 4.5 percent of records, while the registrar was present in 98.3 percent.

#### How many domains can I check at once?

As many as you like. The cap is the **Maximum charged domain records** value you set, up to 50,000 per run, and there is no ten-domain or hundred-domain limit built into the Actor. Requests are paced per registry host and a rate-limited response is retried with backoff; a domain that still cannot be answered comes back free, with the reason on the row.

#### How do I check when a domain expires?

Run the RDAP check and read `expiresAt` and `daysToExpiry`. Both come from the registry's own expiration event, and were present on 97.2 percent of the records we measured. Sort by `daysToExpiry` for a renewal queue, and schedule the run weekly to keep it current.

#### How do I check whether a domain is available?

Read `isRegistered`. `false` means the authoritative registry answered that the name does not exist. `null` means no registry could be reached, and that row is free. Availability is not a price quote: registry-reserved and premium names can also answer as not registered.

#### What format should the domain list be in?

Anything readable. The Domains field takes bare names, full URLs, subdomains, email addresses and internationalized names. The Domain list URL takes plain text with one domain per line, or a CSV whose first column is the domain. Lines starting with `#` are ignored and duplicates are removed before anything is charged.

#### Why does this Actor run with limited permissions?

Least privilege. It only reads and writes its own storages, and it never touches the rest of your account. The optional cross-run memory is a named key-value store the Actor creates for itself on its first run and reads back on later runs.

### API examples

```bash
curl -X POST "https://api.apify.com/v2/acts/automation_craft~domain-whois-rdap-lookup/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domains":["apify.com","github.io"],"checks":["rdap","dns"],"maxItems":50}'
```

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('automation_craft/domain-whois-rdap-lookup').call({
    domains: ['apify.com', 'bbc.co.uk'],
    checks: ['rdap', 'dns', 'tls'],
    dnsRecordTypes: ['A', 'MX', 'TXT'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("automation_craft/domain-whois-rdap-lookup").call(run_input={
    "domainListUrl": "https://example.com/domains.csv",
    "checks": ["rdap"],
    "maxItems": 1000,
    "memoryName": "my-portfolio",
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item.get("domain"), item.get("expiresAt"))
```

### Changelog

**1.0** First release: RDAP for 1,200 TLDs with a ccTLD override table, availability from authoritative 404s, DNS over HTTPS, TLS certificate metadata, port 43 WHOIS fallback, per-field redaction flags and an optional cross-run memory.

### More data tools by Automation Craft

- [US Building Permits Scraper - 38 City Sources](https://apify.com/automation_craft/us-building-permits-scraper)
- [US New Business Registrations Scraper - LLC Leads](https://apify.com/automation_craft/us-new-business-registrations-scraper)
- [UK Food Hygiene Ratings Scraper - FSA Register](https://apify.com/automation_craft/uk-food-hygiene-ratings-scraper)
- [Website Screenshot Scraper: Full Page, Bulk](https://apify.com/automation_craft/website-screenshot-scraper)
- [Google Trends Scraper - Compare and Trending Now](https://apify.com/automation_craft/google-trends-scraper)
- [LinkedIn Jobs Scraper - No Login, Real Dates](https://apify.com/automation_craft/linkedin-jobs-scraper)

# Changelog

This Actor's version history is a separate document: https://apify.com/automation\_craft/domain-whois-rdap-lookup/changelog.md

# Actor input Schema

## `domains` (type: `array`):

The domains to look up. Paste bare names (example.com), full URLs (https://www.example.com/pricing), subdomains, email addresses or internationalized names: each entry is reduced to a hostname automatically. Leave empty and set a domain list URL instead for large lists.

## `domainListUrl` (type: `string`):

A public URL holding one domain per line, or a CSV whose first column is the domain. Lines starting with # are ignored. Merged with the domains above.

## `tldFilter` (type: `array`):

Keep only domains ending in these TLDs, for example com, org, io. Leave empty to look up every domain. Filtered domains are skipped free of charge.

## `checks` (type: `array`):

RDAP is always run: it is the registry answer that decides whether a domain is registered. DNS adds live record lookups over HTTPS, SSL certificate reads the certificate the site serves on port 443, and WHOIS is a port 43 fallback used for TLDs that publish no RDAP service and when a registry's RDAP server does not answer. DNS and SSL cost nothing extra.

## `dnsRecordTypes` (type: `array`):

Which DNS records to read when the DNS check is selected. Each type is one lookup against Cloudflare, with Google as the fallback resolver.

## `includeRaw` (type: `boolean`):

Add the RDAP JSON the registry returned, and the raw WHOIS text under whois.raw where WHOIS was used, to every record the registry answered. Useful for auditing, and it makes the dataset several times larger.

## `maxItems` (type: `integer`):

Hard cap on the number of billable domain records this run delivers. Rows where no registry answered are free and do not count against it.

## `concurrency` (type: `integer`):

How many domains to work on at the same time. Requests to any single RDAP server are paced automatically no matter what you set here, so raising this mainly helps a list that spans many different TLDs.

## `memoryName` (type: `string`):

Optional. Name a memory and every domain this Actor has answered under that name is skipped, free of charge, on later runs. Domains no registry could answer for are not remembered, so they are tried again. Use it to build a large list over several runs, and leave it empty when you want a fresh answer every run, for example a weekly expiry watch.

## `resetMemory` (type: `boolean`):

Empty the named memory first, so every domain in this run counts as new and is delivered again.

## `proxyConfiguration` (type: `object`):

Optional. Registry RDAP servers and the DNS resolvers answer Apify directly, so no proxy is needed and none is used by default. Turn one on only if your own policy requires it. SSL certificate reads and port 43 WHOIS always use the direct connection.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "wikipedia.org",
    "github.io",
    "bbc.co.uk",
    "web.dev"
  ],
  "domainListUrl": "https://example.com/my-domains.csv",
  "checks": [
    "rdap",
    "dns"
  ],
  "dnsRecordTypes": [
    "A",
    "MX",
    "NS"
  ],
  "maxItems": 100,
  "concurrency": 5,
  "memoryName": "my-portfolio"
}
```

# Actor output Schema

## `domains` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "domains": [
        "apify.com",
        "wikipedia.org",
        "github.io",
        "bbc.co.uk",
        "web.dev"
    ],
    "checks": [
        "rdap",
        "dns"
    ],
    "dnsRecordTypes": [
        "A",
        "MX",
        "NS"
    ],
    "includeRaw": false,
    "maxItems": 100,
    "concurrency": 5,
    "resetMemory": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/domain-whois-rdap-lookup").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "domains": [
        "apify.com",
        "wikipedia.org",
        "github.io",
        "bbc.co.uk",
        "web.dev",
    ],
    "checks": [
        "rdap",
        "dns",
    ],
    "dnsRecordTypes": [
        "A",
        "MX",
        "NS",
    ],
    "includeRaw": False,
    "maxItems": 100,
    "concurrency": 5,
    "resetMemory": False,
}

# Run the Actor and wait for it to finish
run = client.actor("automation_craft/domain-whois-rdap-lookup").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "domains": [
    "apify.com",
    "wikipedia.org",
    "github.io",
    "bbc.co.uk",
    "web.dev"
  ],
  "checks": [
    "rdap",
    "dns"
  ],
  "dnsRecordTypes": [
    "A",
    "MX",
    "NS"
  ],
  "includeRaw": false,
  "maxItems": 100,
  "concurrency": 5,
  "resetMemory": false
}' |
apify call automation_craft/domain-whois-rdap-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation_craft/domain-whois-rdap-lookup"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/7h4DKmxxHHyXwnp7z/builds/hR9mpLKM9S3F8fE3f/openapi.json
