# WHOIS Domain Lookup — Bulk Expiry & Registrar Check (RDAP) (`kevinserver24/rdap-domain-intel`) Actor

WHOIS domain lookup at scale: expiration date, registrar, nameservers and lock status read from the registry that owns each extension, over RDAP. You are only charged for domains whose expiry the registry actually returned.

- **URL**: https://apify.com/kevinserver24/rdap-domain-intel.md
- **Developed by:** [Kevin](https://apify.com/kevinserver24) (community)
- **Categories:** Developer tools, SEO tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 verifieds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## RDAP Domain Intelligence — verified expiry, registrar and lock status

*An independent tool. It is not affiliated with, endorsed by, or operated by
ICANN, IANA, Verisign, Nominet, CIRA, DENIC, SWITCH, auDA, SIDN, Norid,
Traficom or any registry or registrar. It queries their public RDAP services
and reports what they return.*

**You are only charged for domains the registry actually answered for.**
No expiration date returned, no charge. That is the whole idea.

This Actor reads domain records over **RDAP**, the official successor to WHOIS
(RFC 7483/9083), straight from the registry that owns the extension — Verisign
for `.com`, Nominet for `.uk`, AFNIC for `.fr`, and so on. No scraping, no
HTML parsing, no third-party middleman who might be a week out of date.

***

### What you get per domain

| Field | Meaning |
|---|---|
| `expiration` | Expiry date, ISO-8601 with timezone |
| `days_until_expiration` | Negative when the domain has already expired |
| `created` | Registration date |
| `last_changed` | Last update recorded by the registry |
| `registrar` | Registrar name, read from the nested vCard |
| `registrar_iana_id` | Its IANA registrar id, when published |
| `nameservers` | Sorted, lowercased, deduplicated |
| `nameserver_count` | |
| `status` | EPP status codes as published |
| `transfer_locked` | `true` when a client or server transfer lock is set |
| `unknown_fields` | Fields the registry did **not** supply — named, never guessed |
| `charged` | Whether this row cost you anything |
| `not_charged_reason` | Why it did not, in plain English |
| `source` | The exact registry URL the answer came from, so you can verify it yourself |

Add `includeRaw` to attach the registry's own JSON to every row, for auditing.
It does not change the price. **Entities carrying personal contact details —
registrant, administrative, technical, billing — are removed before delivery.**
Several country registries publish names, postal addresses, telephone numbers
and email addresses with no redaction of their own; this Actor does not pass
that on, and says so in the row with `_redacted_by_actor`.

***

### The billing rule, stated plainly

Two events:

- **Actor start** — $0.0005, once per run. The competitors that lead this category charge no start fee at all; this one covers the container and the IANA bootstrap read, measured at about $0.00016.
- **Verified domain record** — $0.002, charged **only** when the registry
  returned a usable expiration date. That is below the category median and
  below the market leader, who charges for every lookup whether it answered or
  not.

You are **not** charged when:

- the domain does not exist at the registry,
- the registry returned no expiration date,
- the date came back in a format we refuse to guess at,
- the extension is not covered (see below),
- the registry timed out or errored.

Every one of those still lands in your dataset, with `charged: false` and a
`not_charged_reason` saying exactly what happened. A run that finds nothing
costs you $0.0005.

***

### Coverage, measured rather than claimed

Servers come from the **official IANA bootstrap registry**, re-read at the start
of every run — currently **1 198 extensions**, plus four additions probed and
verified individually that IANA does not list (`.io`, `.me`, `.sh`, `.us`).

On a 102-domain sample spanning eleven extensions, **94 returned a verified
expiration date** and were billable. The eight that did not are all explained
below, and each came back free with that exact sentence in
`not_charged_reason`.

**Registries that serve RDAP but never publish an expiry date.** Local policy,
not an outage. Each was confirmed on five or six separate domains before being
listed:

| Extension | Operator |
|---|---|
| `.de` | DENIC |
| `.ch`, `.li` | SWITCH |
| `.nl` | SIDN |
| `.no` | Norid |
| `.fi` | Traficom |
| `.au` | auDA |

No request is even sent for these, and the row names the operator so you can
confirm it yourself in ten seconds.

**Namespaces administered apart from their parent registry.** `.ac.uk`,
`.gov.uk`, `.nhs.uk`, `.mod.uk`, `.police.uk` and `.sch.uk` are not run by
Nominet and publish no RDAP service of their own. Ordinary `.uk` and `.co.uk`
domains are fully supported — `bbc.co.uk` returns its expiry normally.

**Excluded by registry terms.** `.ca` — CIRA's RDAP terms forbid this kind of
batch, commercial lookup (see "Registry terms" below). No request is ever sent
for a `.ca` domain; the row comes back free, naming CIRA and the clause.

**Extensions with no public RDAP at all**: `.co`, `.jp`, `.cn`, `.ru`, `.es`,
`.it`, `.se`, `.dk`, `.at`, `.be`, `.nz`, `.za` and others. Probed one by one on
2026-08-18 against every published operator address; none answered.

This is a property of the RDAP ecosystem, not of this Actor. It is stated here
rather than discovered on your invoice.

### Typical uses

- **Portfolio monitoring** — check the domains you own or manage and alert on
  `days_until_expiration < 30`.
- **Acquisition** — find out when a name you want actually drops.
- **Due diligence** — confirm a counterparty's domains are locked and not
  about to lapse.
- **Security review** — spot nameserver changes and missing transfer locks
  across an estate.

***

### Input

```json
{
  "domains": ["google.com", "lemonde.fr", "bbc.co.uk", "github.io"],
  "includeRaw": false
}
```

Give **registrable domains**: `google.com`, `bbc.co.uk`. A URL is reduced to
its host, but the host is then queried as written — `www.google.com` is not the
same name as `google.com`, and no registry holds a record for it. Strip the
`www.` yourself.

Duplicates, casing and trailing dots are normalised, so `Google.COM`,
`google.com.` and `google.com` count as one domain and are charged once. A run
is capped at **5000 domains**; beyond that the first 5000 are kept and the log
says so.

Set a **maximum cost per run** in the Actor's settings and the run stops
cleanly when it is reached, telling you how many domains were left untouched.
Your list order is preserved, so put what matters first.

***

### Output

```json
{
  "domain": "google.com",
  "expiration": "2028-09-14T04:00:00+00:00",
  "days_until_expiration": 757,
  "created": "1997-09-15T04:00:00+00:00",
  "last_changed": "2019-09-09T15:39:04+00:00",
  "registrar": "MarkMonitor Inc.",
  "registrar_iana_id": "292",
  "nameservers": ["ns1.google.com", "ns2.google.com", "ns3.google.com", "ns4.google.com"],
  "nameserver_count": 4,
  "status": ["client delete prohibited", "client transfer prohibited"],
  "transfer_locked": true,
  "unknown_fields": [],
  "charged": true,
  "not_charged_reason": "",
  "source": "https://rdap.verisign.com/com/v1"
}
```

A `SUMMARY.json` lands in the key-value store with the counts and a breakdown
of every reason a domain was not charged — so a registry outage looks like a
registry outage, not like a quiet drop in your results.

***

### Rate limits and politeness

Requests are spaced **per registry**, not globally. A hundred `.com` domains
are a hundred requests to one registry and are paced accordingly; a hundred
domains spread over twenty extensions are only five per registry and run at
full speed. Transient `429`/`5xx` responses are retried.

A registry that answers `429` gets **more room, and only that registry** — the
delay for it doubles and stays doubled for the rest of the run, while every
other registry keeps running at full speed.

A `404` is taken as that registry's final answer, and the message says **which
registry** answered — because "not found" is only ever a fact about one server,
never about the world. Measured throughput: **102 domains across eleven
extensions in 36.7 seconds**.

***

### Registry terms — read this before you scale

Registry RDAP services are published so that people can look up domains, and
several registries put limits in writing on what may be done with the answers.
**`.ca` is excluded entirely as of 2026-08-19**: CIRA permits RDAP use only to
identify or contact a domain holder and prohibits aggregation, commercial and
market-research use, and automated multi-query access — exactly what a batch
lookup tool does, regardless of how the customer intends to use one row of it.
A `.ca` domain in your input list comes back free, with that reason, and no
request is ever sent to CIRA's registry. The `.us` registry prohibits
high-volume automated processes. Verisign reserves the right to restrict
access at its sole discretion.

Checking domains **you own or administer** is the use this Actor is built for.
Compiling registrant data across domains you have no relationship with is not,
and this Actor is not sold for it. The practical consequence of ignoring that
is not a courtroom — it is an IP block, which is a reliability problem for
everyone using the tool.

***

### Why trust the numbers

Every row that got an answer names the registry URL it came from in `source`.
Rows that got no answer carry the reason instead. Check any of them yourself:

```bash
curl -H 'Accept: application/rdap+json' https://rdap.verisign.com/com/v1/domain/google.com
```

You should get the same expiration date this Actor reported.

# Actor input Schema

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

One registrable domain per line, e.g. google.com or bbc.co.uk. Give the bare domain: a host such as www.google.com is queried exactly as written and the registry will not know it. Up to 5000 domains per run. You are NOT charged for a domain the registry could not answer for.

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

Attach the registry's own JSON response to every row, for auditing. It makes the dataset much larger and does not change the price. Entities carrying personal contact details (registrant, administrative, technical, billing) are REMOVED before delivery: several country registries publish names, postal addresses, phone numbers and emails without redacting them, and this Actor does not pass that on.

## Actor input object example

```json
{
  "domains": [
    "google.com",
    "wikipedia.org",
    "bbc.co.uk",
    "github.io"
  ],
  "includeRaw": false
}
```

# Actor output Schema

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

One row per submitted domain. Every row carries `charged` and, when it is false, a `not_charged_reason` in plain English -- a registry that publishes no expiry date, a domain the registry holds no record for, an extension with no public RDAP service. `source` names the registry the answer came from, so any row can be checked at the source.

## `resultsCsv` (type: `string`):

The same rows as a spreadsheet. Column order is stable: successful and unanswered rows carry exactly the same fields, so nothing shifts when a registry fails.

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

How many domains were requested, charged, free and left untouched by the spending limit, with a breakdown of every reason a domain was not charged. The four counts always add up to the number requested, so a registry outage looks like a registry outage rather than a quiet drop in results.

# 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": [
        "google.com",
        "wikipedia.org",
        "bbc.co.uk",
        "github.io"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kevinserver24/rdap-domain-intel").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": [
        "google.com",
        "wikipedia.org",
        "bbc.co.uk",
        "github.io",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("kevinserver24/rdap-domain-intel").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": [
    "google.com",
    "wikipedia.org",
    "bbc.co.uk",
    "github.io"
  ]
}' |
apify call kevinserver24/rdap-domain-intel --silent --output-dataset

```

## MCP server setup

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

```

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/hnqBhFzwMGYns9MBs/builds/WkFSKvc7KnfSvEhIN/openapi.json
