# WHOIS Domain Lookup & Domain Age Checker (Bulk, RDAP) (`lyfe_tools/domain-age-whois-rdap-bulk`) Actor

WHOIS domain lookup in bulk via RDAP: domain age, registration and expiry dates, registrar, nameservers and DNSSEC from the registries' own servers, with a new / young / established signal for cold email. No personal data.

- **URL**: https://apify.com/lyfe_tools/domain-age-whois-rdap-bulk.md
- **Developed by:** [LYFE Offshore](https://apify.com/lyfe_tools) (community)
- **Categories:** SEO tools, Lead generation, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.56 / 1,000 domain checkeds

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

## Domain Age Checker & Bulk WHOIS Lookup (RDAP)

Domain Age Checker and bulk WHOIS lookup over RDAP: for a list of domains, get the registration date, expiry date, registrar, nameservers and DNSSEC of each one, plus a `new` / `young` / `established` age signal.

The answers come straight from the registries' own RDAP servers (the official successor to WHOIS).
A domain registered three weeks ago sending cold mail is one of the oldest spam signals there is.

**No personal data.** RDAP answers sometimes contain the registrant's name or contact details. This
Actor never copies them out: it reads only dates, status flags, nameservers, DNSSEC and the
registrar's company name, and drops every registrant, admin, tech and abuse contact. There is a
test for exactly that.

### One run, real output

Eleven domains across `.com`, `.org`, `.nl`, `.co.uk`, `.dev`, `.app`, `.shop`, `.de`, `.io`, run
for real on 4 October 2026. Eight were looked up, two sit on TLDs with no RDAP server, one is not registered.

| Domain | Status | Registered | Age (years) | Signal | Registrar | DNSSEC |
|---|---|---|---|---|---|---|
| stripe.com | ok | 1995-09-12 | 31.1 | established | SafeNames Ltd. | no |
| mozilla.org | ok | 1998-01-24 | 28.7 | established | MarkMonitor Inc. | no |
| rijksoverheid.nl | ok | 1999-07-06 | 27.2 | established | Rijksoverheid | yes |
| bbc.co.uk | ok | 1994-12-13 | 31.8 | established | British Broadcasting Corporation | no |
| web.dev | ok | 2018-10-29 | 7.9 | established | MarkMonitor Inc. | no |
| cash.app | ok | 2018-03-29 | 8.5 | established | CSC Corporate Domains, Inc. | no |
| allbirds.shop | ok | 2026-08-22 | 0.1 | **young** | Spaceship, Inc. | no |
| lyfeoffshore.com | ok | 2026-05-13 | 0.4 | **young** | Cloudflare, Inc. | no |
| spiegel.de | tld_without_rdap | | | | | |
| github.io | tld_without_rdap | | | | | |
| nonexistent-zz-98765-foo.com | not_registered | | | | | |

You were billed for the eight rows with facts. The three others cost nothing.

### Input example

This is the prefilled input of the run above:

```json
{
  "domains": ["stripe.com", "mozilla.org", "rijksoverheid.nl", "bbc.co.uk", "web.dev", "cash.app",
              "allbirds.shop", "lyfeoffshore.com", "spiegel.de", "github.io", "nonexistent-zz-98765-foo.com"],
  "outputMode": "all"
}
```

`domains` takes names, URLs or email addresses (`https://www.mozilla.org/` and `someone@bbc.co.uk`
both work). Or give `inputDatasetId` (and `domainField`) to read domains from another Actor's
dataset; without `domainField` it uses the `domain`, `website`, `url` or `email` column.
`outputMode: "found-only"` returns just the rows with facts. `concurrency` (default 20) sets the
lookups in flight overall.

### Output example

One real row (`stripe.com`, looked up locally with this Actor's code on 5 October 2026) and one free row:

```json
{
  "domain": "stripe.com",
  "tld": "com",
  "rdapServer": "https://rdap.verisign.com/com/v1/",
  "status": "ok",
  "billable": true,
  "registered": "1995-09-12T04:00:00Z",
  "expires": "2027-09-11T04:00:00Z",
  "lastChanged": "2025-10-01T01:39:51Z",
  "ageDays": 11346,
  "ageYears": 31.1,
  "ageSignal": "established",
  "statusFlags": ["client delete prohibited", "client transfer prohibited", "client update prohibited",
                  "server delete prohibited", "server transfer prohibited", "server update prohibited"],
  "registrar": "SafeNames Ltd.",
  "nameservers": ["ns-1087.awsdns-07.org", "ns-1882.awsdns-43.co.uk", "ns-423.awsdns-52.com", "ns-705.awsdns-24.net"],
  "dnssec": false
}
```

```json
{ "domain": "spiegel.de", "tld": "de", "status": "tld_without_rdap", "billable": false,
  "note": ".de has no RDAP server in the IANA bootstrap list" }
```

| Field | Meaning |
|---|---|
| `domain` | The domain as looked up (lowercase, `www.` removed, IDN in punycode) |
| `tld` | Top-level domain |
| `status` | Row status, see the table below |
| `billable` | `true` only for rows you pay for (`status: ok`) |
| `registered` | Registration date from the registry |
| `expires` | Expiry date |
| `lastChanged` | Last time the registry record changed |
| `ageDays` / `ageYears` | Age since `registered` |
| `ageSignal` | `new`, `young`, `established` or `unknown` |
| `registrar` | Registrar's company name (empty when the registry leaves it out) |
| `nameservers` | Nameservers, sorted |
| `dnssec` | `true` if the delegation is signed, `false` if not, `null` if the registry does not say |
| `statusFlags` | EPP status flags such as `client hold`, `pending delete` |
| `rdapServer` | The RDAP server that answered |
| `note` | Why a row has no facts |

A run summary (counts per status, TLDs without RDAP, date of the IANA list) is stored under the `SUMMARY` key.

### Age signal

| Signal | Age | Reading |
|---|---|---|
| `new` | under 30 days | high risk for cold email, check before you send |
| `young` | 30 to 364 days | treat with care |
| `established` | one year or more | no age warning |
| `unknown` | no registration date | the registry did not give one |

It is one signal, not a verdict: plenty of legitimate businesses register a fresh domain, and a
spammer can buy an old one. Pair it with an address check, for example
[Catch-All Email Verifier](https://apify.com/lyfe_tools/catchall-email-verifier).

### Which TLDs work (and which do not)

The Actor finds each domain's RDAP server in the [IANA bootstrap list](https://data.iana.org/rdap/dns.json)
(version of 30 September 2026, 1,203 TLDs). **A TLD that is not in that list has no RDAP server, and the row
says `tld_without_rdap` and is free.** Checked on 4 October 2026 against 59 popular TLDs:

- **Work:** `com net org info biz ai app dev shop store online site tech xyz tv uk nl fr no fi pl cz ua ca au kr in br ar sg tw id`
- **Do not work:** `io co me us de be ch at eu es it pt se dk ru tr gr ie nz jp cn mx cl za il ae hk`

Several of the popular ones that do not work, `.de`, `.io`, `.co` and `.eu` among them, are
widely used for business domains. If most of your list is `.de`, this Actor is not for you yet.
Lookups also tested live: `.fr .no .ca .br .in .cz .pl .kr` returned data, and
`.au` returned registrar and status but **no dates** for a `.net.au` name (row status `no_dates`, free).
Some registries leave out the registrar name (`.br`, `.cz` in our test), and the field is then empty.

### Row statuses

| Status | Meaning | Billed |
|---|---|---|
| `ok` | registration facts returned | yes |
| `tld_without_rdap` | no RDAP server for this TLD | no |
| `not_registered` | the registry says the domain does not exist | no |
| `no_dates` | the registry answered without registration or expiry dates | no |
| `rate_limited` / `lookup_failed` | the server refused or failed after retries; try again later | no |
| `invalid_domain` | not a domain name | no |

If **every** lookup fails (no network, all servers down), the run fails with a message instead of
finishing empty. Domains are looked up once even if they appear twice. Subdomains are not reduced
to the registrable domain: give `example.com`, not `shop.example.com`.

### Politeness

Each RDAP server gets at most 2 requests at a time with a short pause between them, a `429` or `503`
is retried with backoff (honouring `Retry-After`), and every request says who it is in the
`User-Agent` (`tools@lyfeoffshore.com`). These are the registries' public servers; please do not
point huge lists at a single TLD in a tight loop. A 10,000-domain `.com` list takes a while by design.

### Price

Price per 1,000 by your Apify plan (Apify applies the right column automatically):

| | Free | Starter | Scale | Business and up |
|---|---|---|---|---|
| Domain checked, per 1,000 | $0.80 | $0.80 | $0.68 | $0.56 |

No start fee. The figures below use the Free-plan price; on a paid plan the same run costs less.

| Event | Price |
|---|---|
| **Domain checked** (registration facts returned) | **$0.0008** |

- 1,000 domains with facts ≈ $0.80. 10,000 ≈ $8.
- The 11-domain run above: 8 billed rows = $0.0064.
- Free: `tld_without_rdap`, `not_registered`, `no_dates`, `rate_limited`, `lookup_failed` and `invalid_domain` rows, in both output modes.

Platform usage is included. A run's max-charge limit (`ACTOR_MAX_TOTAL_CHARGE_USD`) caps how many
domains are processed; the rest are skipped with a warning in the log.

### Use it from code or an AI agent

```bash
curl -X POST "https://api.apify.com/v2/acts/lyfe_tools~domain-age-whois-rdap-bulk/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["stripe.com", "allbirds.shop"]}'
```

It returns the rows as a JSON array. AI agents (Claude, Cursor and other MCP clients)
can call the Actor through the Apify MCP server at https://mcp.apify.com: the agent sends `domains`
and reads `status`, `registered`, `expires`, `ageSignal` and `registrar`.

### FAQ

**How do I check the age of many domains at once?**
Put them in `domains` or point `inputDatasetId` at another Actor's dataset. Each domain gets one row.

**Is this a WHOIS lookup?**
It uses RDAP, the registries' JSON successor to WHOIS. You get the same registration and expiry
dates and registrar, without parsing WHOIS text and without registrant contact data.

**Why does my .de or .io domain show `tld_without_rdap`?**
Those registries have no RDAP server in the IANA bootstrap list, so there is nothing to ask. The row is free.

**Can I find who owns a domain?**
No. Registrant, admin, tech and abuse contacts are dropped on purpose. Only the registrar's company name is kept.

**How do I spot newly registered domains for spam or cold email checks?**
Filter on `ageSignal`: `new` is under 30 days, `young` under a year.

**Am I charged for domains that are not registered?**
No. Only rows with `status: ok` are billed.

Next step: check the domain's email setup with
[SPF, DKIM & DMARC Checker](https://apify.com/lyfe_tools/domain-email-health-check).

### Source and terms

Data comes from the registries' public RDAP servers, found through IANA's bootstrap list (RFC 9224).
RDAP is a public protocol meant for this kind of lookup, but each registry publishes its own terms
and rate limits, and we have not read all of them. Registry data is not ours; use it for the lookups
you need rather than to rebuild a registry. The `registered` date is what the registry holds, which
can differ from when a site first went live (domains change hands and are re-registered).

Issues and questions: tools@lyfeoffshore.com.

# Actor input Schema

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

Domain names or URLs or email addresses (the domain is taken from them). Duplicates are looked up once. Subdomains are not reduced to the registrable domain: give example.com, not shop.example.com.

## `inputDatasetId` (type: `string`):

Read domains from an existing dataset, for example another Actor's output (column set below, else `domain`, `website`, `url` or `email`). They are added to any domains listed above.

## `domainField` (type: `string`):

Column holding the domain in the dataset. Default: domain.

## `outputMode` (type: `string`):

all: one row per domain, including those that could not be looked up (with the reason). found-only: only domains with registration facts. You are billed only for domains with facts, in both modes.

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

Lookups in flight overall. Each RDAP server is additionally limited to 2 at a time with a pause between requests, whatever you set here.

## Actor input object example

```json
{
  "domains": [
    "stripe.com",
    "mozilla.org",
    "rijksoverheid.nl",
    "bbc.co.uk",
    "web.dev",
    "cash.app",
    "allbirds.shop",
    "lyfeoffshore.com",
    "spiegel.de",
    "github.io",
    "nonexistent-zz-98765-foo.com"
  ],
  "outputMode": "all",
  "concurrency": 20
}
```

# Actor output Schema

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

One row per domain: registration date, age, expiry, registrar, nameservers, DNSSEC, status flags and an age signal.

## `summary` (type: `string`):

Counts per status and which TLDs have no RDAP server.

# 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": [
        "stripe.com",
        "mozilla.org",
        "rijksoverheid.nl",
        "bbc.co.uk",
        "web.dev",
        "cash.app",
        "allbirds.shop",
        "lyfeoffshore.com",
        "spiegel.de",
        "github.io",
        "nonexistent-zz-98765-foo.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lyfe_tools/domain-age-whois-rdap-bulk").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": [
        "stripe.com",
        "mozilla.org",
        "rijksoverheid.nl",
        "bbc.co.uk",
        "web.dev",
        "cash.app",
        "allbirds.shop",
        "lyfeoffshore.com",
        "spiegel.de",
        "github.io",
        "nonexistent-zz-98765-foo.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("lyfe_tools/domain-age-whois-rdap-bulk").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": [
    "stripe.com",
    "mozilla.org",
    "rijksoverheid.nl",
    "bbc.co.uk",
    "web.dev",
    "cash.app",
    "allbirds.shop",
    "lyfeoffshore.com",
    "spiegel.de",
    "github.io",
    "nonexistent-zz-98765-foo.com"
  ]
}' |
apify call lyfe_tools/domain-age-whois-rdap-bulk --silent --output-dataset

```

## MCP server setup

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

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/8kffoT576aEExj4OL/builds/yBNsqPjvV55Dk2w7o/openapi.json
