# Bulk WHOIS Lookup (RDAP) - Domain Expiry, Age, Registrar, CSV (`kamerozkan/bulk-whois-lookup-rdap`) Actor

Bulk WHOIS lookup over RDAP for any TLD: paste domains, URLs or a CSV column and get registrar, creation and expiry dates, domain age, days to expiry, EPP status, nameservers, DNSSEC, registrant organization and country, plus A, MX and NS records. Unregistered domains are flagged as available.

- **URL**: https://apify.com/kamerozkan/bulk-whois-lookup-rdap.md
- **Developed by:** [Kamer Ozkan](https://apify.com/kamerozkan) (community)
- **Categories:** Developer tools, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 domain lookups

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 Lookup (RDAP) - Domain Expiry, Age, Registrar, CSV

Run a **bulk WHOIS lookup** for any list of domains and export it as **CSV**, Excel or JSON: registrar, creation and expiry dates, days to expiry, domain age, EPP status codes and transfer lock, nameservers, DNSSEC, registrant organization and country, plus the live A, MX and NS records. Paste domains, full URLs or e-mail addresses; subdomains and `www` are reduced to the registrable domain, and domains the registry does not know come back as **available**. Data comes from the registries' public **RDAP** servers (the structured successor of WHOIS) for every TLD listed by IANA, with the registrar record merged in for `.com`, `.net` and other thin registries. No API key, no proxy, no login.

### What does Bulk WHOIS Lookup do?

WHOIS as a text protocol is rate limited, inconsistent and often empty since GDPR. RDAP is the same registration data as JSON, served by every registry (Verisign for `.com` and `.net`, PIR for `.org`, Nominet for `.uk`, DENIC for `.de`, SWITCH for `.ch`, AFNIC for `.fr`, SIDN for `.nl` and about 1,200 more TLDs). This actor reads the IANA bootstrap file to find the right server for each domain, asks the registry, follows the link to the registrar's record when the registry keeps only a thin entry, and turns both answers into one flat row per domain. Three light DNS-over-HTTPS requests add the current A, MX and NS records, so you also see where the domain points and whether it receives mail.

Typical runs: enrich a lead list with domain age and registrar, find which domains in a portfolio expire in the next 90 days, check which names from a brainstorm are still free, or audit the nameservers and transfer locks of every domain your company owns.

### Who is it for?

- **Domain investors and brokers**: expiry dates, age, registrar and transfer lock of whole watchlists; available names flagged.
- **Hosting providers, MSPs and IT teams**: a scheduled portfolio check that returns only the domains expiring within N days.
- **SEO, link building and brand protection**: domain age, registrant organization and country, nameservers and MX of competitor and lookalike domains.
- **Security and fraud teams**: registration date, registrar abuse contact, privacy-proxy flag and DNS footprint of suspicious domains in bulk.
- **Sales and data teams**: enrich CRM domains with registrar, age and e-mail readiness (MX) in one run.

### What data can you get?

| Field | Example |
|---|---|
| `domain`, `inputValue`, `tld` | apify.com, https://www.apify.com/store, com |
| `status`, `registered` | registered or available, true/false |
| `registrar`, `registrarIanaId`, `registrarUrl` | Amazon Registrar, Inc., 468, http://registrar.amazon.com |
| `registrarAbuseEmail`, `registrarAbusePhone` | trustandsafety@support.aws.com, +1.2024422253 |
| `createdAt`, `updatedAt`, `expiresAt`, `lastTransferAt` | 2009-06-02T17:14:10Z, 2026-05-16T16:53:04Z, 2035-06-02T17:14:10Z, null |
| `daysUntilExpiry`, `domainAgeYears`, `expiringSoon` | 3148, 17.4, false (true when 30 days or less remain) |
| `eppStatuses`, `transferLock` | \["clientTransferProhibited"], true |
| `nameservers`, `dnssec` | \["ns-449.awsdns-56.com", ...], true |
| `registrantOrganization`, `registrantCountry`, `registrantState`, `privacyProtected` | GitHub, Inc., US, CA, false (organization only, never a person's name, e-mail or phone) |
| `dnsA`, `dnsMx`, `dnsNs` | \["18.66.102.61", ...], \["mx1.nic.fr", "mx2.nic.fr"], \["ns1.wikimedia.org", ...] |
| `rdapRegistryUrl`, `rdapRegistrarUrl`, `rdapSource` | the two RDAP records behind the row, registry+registrar or registry |
| `checkedAt` | 2026-10-19T12:20:35Z |

What a registry publishes differs by TLD: `.com`, `.net`, `.org`, `.io`, `.fr`, `.nl`, `.uk` and most gTLDs return dates and statuses; `.de` and `.ch` return nameservers and status only (no creation or expiry date, no registrant). Fields that a registry does not publish are `null` or `[]`.

### How much does it cost?

You pay per answered domain: **$2.00 per 1,000 domains** ($0.002 per domain) on the Free plan, with Store discounts on paid Apify plans: Bronze $1.80, Silver $1.70, Gold and above $1.60 per 1,000 domains. Platform usage is included. The registrar record and the DNS records are part of the row and cost nothing extra.

Worked examples:

- A lead list of 500 domains with registrar, age, expiry and MX: 500 x $0.002 = **$1.00**.
- A weekly expiry check of a 2,000-domain portfolio with **Only domains expiring within N days** set to 90, where 35 domains qualify: 35 x $0.002 = **$0.07 per week**. The other 1,965 are checked but not charged.
- 50 brainstormed names, 38 of them still available: 50 x $0.002 = **$0.10**.

Domains whose TLD has no public RDAP server (for example `.at`, `.co`, `.eu`, `.it`, `.es`, `.ru`, `.jp`, `.cn`, `.us`, `.me`, `.se`) and lookups that fail are listed in the run's OUTPUT record and are **never charged**. Set **Max domains** or a maximum cost per run to cap spending.

### How to use it

1. Click **Try for free**.
2. Paste your domains into **Domains**, one per line (a CSV column works as is; URLs and e-mail addresses are fine).
3. Optional: turn off the registrar record or DNS records for a faster registry-only run, turn off available domains, or set **Only domains expiring within N days** for a portfolio check.
4. Click **Start** and download the table as CSV, Excel, JSON or through the API. The **Expiry watch** view shows expiry date, days left, lock status and registrar side by side.

### Input example

```json
{
  "domains": ["apify.com", "https://www.heise.de/", "github.io", "wikipedia.org", "example.co.uk", "this-domain-is-surely-free-98765.com"],
  "includeRegistrarRecord": true,
  "includeDns": true,
  "includeAvailable": true,
  "onlyExpiringWithinDays": 0,
  "maxItems": 100
}
```

### Output example

Two unedited rows from a live run (lists shortened):

```json
{
  "domain": "github.io",
  "inputValue": "github.io",
  "tld": "io",
  "status": "registered",
  "registered": true,
  "registrar": "MarkMonitor Inc.",
  "registrarIanaId": "292",
  "registrarUrl": "https://www.markmonitor.com/about-us/",
  "registrarAbuseEmail": "abusecomplaints@markmonitor.com",
  "registrarAbusePhone": "+1.2083895740",
  "createdAt": "2013-03-08T19:12:48Z",
  "updatedAt": "2026-08-12T09:08:04Z",
  "expiresAt": "2027-03-08T00:00:00Z",
  "lastTransferAt": null,
  "daysUntilExpiry": 139,
  "domainAgeYears": 13.6,
  "expiringSoon": false,
  "eppStatuses": ["clientDeleteProhibited", "clientTransferProhibited", "clientUpdateProhibited"],
  "transferLock": true,
  "nameservers": ["ns-692.awsdns-22.net", "ns-1622.awsdns-10.co.uk", "dns1.p05.nsone.net"],
  "dnssec": false,
  "registrantOrganization": "GitHub, Inc.",
  "registrantCountry": "US",
  "registrantState": "CA",
  "privacyProtected": false,
  "dnsA": ["185.199.108.153", "185.199.109.153"],
  "dnsMx": [],
  "dnsNs": ["dns1.p05.nsone.net", "ns-692.awsdns-22.net"],
  "rdapRegistryUrl": "https://rdap.identitydigital.services/rdap/domain/github.io",
  "rdapRegistrarUrl": "https://rdap.markmonitor.com/rdap/domain/github.io",
  "rdapSource": "registry+registrar",
  "checkedAt": "2026-10-19T12:20:35Z"
}
{
  "domain": "this-domain-is-surely-free-98765.com",
  "inputValue": "this-domain-is-surely-free-98765.com",
  "tld": "com",
  "status": "available",
  "registered": false,
  "registrar": null,
  "createdAt": null,
  "expiresAt": null,
  "daysUntilExpiry": null,
  "eppStatuses": [],
  "nameservers": [],
  "rdapRegistryUrl": "https://rdap.verisign.com/com/v1/domain/this-domain-is-surely-free-98765.com",
  "rdapSource": null,
  "checkedAt": "2026-10-19T12:20:35Z"
}
```

### Integrations and API

Run it from your code with the Apify API or the `apify-client` package, schedule it in Apify (weekly expiry checks), or connect it to Make, n8n, Zapier, Google Sheets or a webhook. With **Only domains expiring within N days** the dataset of a scheduled run holds only the domains that need action, which is exactly what a Slack or e-mail alert wants.

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("kamerozkan/bulk-whois-lookup-rdap").call(run_input={
    "domains": ["apify.com", "wikipedia.org", "github.io"],
    "includeDns": True,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["domain"], item["status"], item["registrar"], item["expiresAt"], item["daysUntilExpiry"])
```

### FAQ

**Is this real WHOIS data?** Yes. RDAP is the registries' official replacement for the WHOIS protocol and serves the same registration record as JSON. For thin registries the registrar's RDAP record is read too, so you get the registrant organization and the registrar's own expiry date where WHOIS would show only a referral.

**Why are some fields empty?** Each registry decides what it publishes. Since GDPR most registrant details are redacted or behind a privacy proxy (`privacyProtected` is then true); country, state and organization are usually still visible. `.de` and `.ch` publish no dates at all.

**Which TLDs work?** Every TLD in the IANA RDAP bootstrap file (about 1,200, all gTLDs and most ccTLDs) plus `.de`, `.ch`, `.li` and `.io`. A few ccTLDs still run no public RDAP server; those domains are reported in OUTPUT as unsupported and are not charged.

**Does "available" mean I can register it?** The registry has no record for that exact name, which is the strongest public signal that it is free. Reserved and premium names can still be blocked at registration time.

**Can I check subdomains or full URLs?** Yes. `https://blog.example.co.uk/post` becomes `example.co.uk`; `info@example.org` becomes `example.org`. Duplicates are looked up once.

**How fast is it?** Up to 10 lookups in parallel, with at most 3 requests at a time per RDAP server to stay polite. A thousand `.com` domains with registrar and DNS records take a few minutes.

**Is it legal to collect this data?** It collects publicly served registration and DNS data only, keeps registrant data to organization, country and state, and never returns names, e-mails or phone numbers of private persons. You are responsible for how you use it, including privacy laws like GDPR and the registries' acceptable-use terms.

**Something is not working?** Open an issue on the Issues tab with your run link and we will fix it fast.

### Support

Questions, a TLD that answers oddly, or a field you need (for example TXT records for SPF and DMARC, or SSL certificate expiry)? Open an issue on the **Issues** tab of this actor with your run link. Issues are read every working day and fixes ship within a few days; feature requests that other users share get built.

# Actor input Schema

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

One per line: a domain (apify.com), a full URL (https://www.heise.de/news) or an e-mail address (info@example.org). Subdomains and www are reduced to the registrable domain. Paste a CSV column directly.

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

Stop after this many answered domains. You are charged per answered domain, so this also caps the cost of the run.

## `includeRegistrarRecord` (type: `boolean`):

For thin registries (.com, .net and others) the registry answer only links to the registrar's RDAP record. Reading it adds the registrant organization, country and state, the registrar abuse contact and the registrar's expiry date. One extra request per domain.

## `includeDns` (type: `boolean`):

Resolve A, MX and NS records of every registered domain through public DNS-over-HTTPS. Three light requests per domain.

## `includeAvailable` (type: `boolean`):

Domains the registry does not know are returned with status 'available' (and charged like a lookup). Turn off to get registered domains only.

## `onlyExpiringWithinDays` (type: `integer`):

0 = off. Set to 30, 60 or 90 to get only registered domains whose expiry date is that close: the cheap way to monitor a portfolio on a schedule. Other domains are checked but not returned or charged.

## `pushErrorRows` (type: `boolean`):

Domains whose TLD has no public RDAP server or whose lookup failed are always listed in the OUTPUT record and never charged. Turn on to also get them as dataset rows with status 'error'.

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

Lookups in flight at once (1 to 10). Each RDAP server is asked at most 3 requests at a time whatever you set here.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "https://www.heise.de/",
    "github.io",
    "wikipedia.org",
    "example.co.uk",
    "this-domain-is-surely-free-98765.com"
  ],
  "maxItems": 100,
  "includeRegistrarRecord": true,
  "includeDns": true,
  "includeAvailable": true,
  "onlyExpiringWithinDays": 0,
  "pushErrorRows": false,
  "concurrency": 5
}
```

# Actor output Schema

## `results` (type: `string`):

One row per result in the default dataset.

# 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",
        "https://www.heise.de/",
        "github.io",
        "wikipedia.org",
        "example.co.uk",
        "this-domain-is-surely-free-98765.com"
    ],
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("kamerozkan/bulk-whois-lookup-rdap").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",
        "https://www.heise.de/",
        "github.io",
        "wikipedia.org",
        "example.co.uk",
        "this-domain-is-surely-free-98765.com",
    ],
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("kamerozkan/bulk-whois-lookup-rdap").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",
    "https://www.heise.de/",
    "github.io",
    "wikipedia.org",
    "example.co.uk",
    "this-domain-is-surely-free-98765.com"
  ],
  "maxItems": 100
}' |
apify call kamerozkan/bulk-whois-lookup-rdap --silent --output-dataset

```

## MCP server setup

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

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/2hTRslZgxJZxmzShX/builds/ax9A5qBRthTPmSgij/openapi.json
