# Domain RDAP Lookup & Expiry Watch (`mikee368/domain-rdap-watch`) Actor

Monitor your own domain portfolio: registrar, status, dates, and nameservers, plus expiry and registration changes. Hard cap of 5,000 domains. Not a lead list.

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

## Pricing

from $1.50 / 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.

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

### What does Domain RDAP Lookup & Expiry Watch do?

**Look up domains you operate** and, when you want, **watch them for expiry and registration changes**. Each name is read from the registry's RDAP service (the Registration Data Access Protocol), using the [IANA RDAP bootstrap](https://data.iana.org/rdap/dns.json) plus hand-added servers for `.de`, `.ch`, `.us` and `.co`.

This is a portfolio monitor. It is not a lead list, not a bulk availability checker, and not a source of registrant contacts. Registrant and contact entities are dropped and are never written to the dataset or to the saved snapshot.

### Why use an RDAP lookup for your own domains?

- One row per domain: registrar, IANA registrar id when published, created / updated / expiry times, EPP statuses, nameservers and whether DNSSEC delegation is signed.
- An honest `supportLevel` (`full`, `limited` or `unsupported`) instead of a silent empty date.
- Watch mode stores one snapshot per watchlist and emits a typed change alert: expiry soon, registrar, nameservers, status, DNSSEC, dropped, or newly registered.
- Scheduling, dataset export and the Apify API are available on the platform. The Actor does not use proxies.

### What data can you extract?

| Field | Meaning |
|---|---|
| `domain`, `tld`, `registered` | Name, registry suffix, and whether RDAP returned 200 (true) or 404 (false). Null if the name is unsupported or the registry was blocked. |
| `registrar`, `registrarIanaId` | Registrar only. No registrant, admin or tech contacts. |
| `createdAt`, `expiresAt`, `updatedAt`, `daysToExpiry`, `domainAgeDays` | Registry events. `.nl` and `.de` do not publish an expiry date. |
| `statuses`, `nameservers`, `dnssecSigned` | EPP statuses and nameservers (lower case, no trailing dot). |
| `supportLevel`, `error` | `full`, `limited` (`.de`, `.ch`) or `unsupported`. `source_unavailable` means this run could not use that registry. |
| `changeType`, `field`, `before`, `after` | Watch alert. Only the field that changed is included. |

A 404 means the RDAP server has no registration record. It does **not** mean the name is available to register. Reserved and premium names can also 404.

### How to look up your domains

1. Open the Actor and add the domain names you administer under **Domains you operate** (maximum 5,000 per run).
2. Leave **Mode** on **Lookup** for a one-off check, or switch to **Watch** and set a **Watchlist ID** you will reuse.
3. For watch runs, keep **Expiry window** at 30 days (or widen it) and choose the alert types you want. Schedule the same input daily or weekly.
4. Run the Actor. Download the dataset as JSON, CSV, Excel or via the API. The key `RUN_SUMMARY` in the default key-value store has the counts.

The first watch run stores a baseline and emits no alerts. Later runs emit one alert per change. `EXPIRY_SOON` fires once per domain per expiry date.

### How much will it cost to look up domains?

Pay per event, with no start fee. Runs use **128 MB** of memory and no proxies.

| Event | When it is charged | Price |
|---|---|---|
| `domain-lookup` | RDAP answered HTTP 200 or 404 | $0.0015 |
| `change-alert` | One watch-mode alert row | $0.003 |

Invalid names, unsupported endings and blocked registries are not lookup events. A charge limit stops the run cleanly: rows that were not stored are not marked as seen, so the next run can deliver them instead of charging them twice or dropping them. Do not also price the default dataset-item event, or error rows would be billed as well.

A lookup of 100 of your domains is about two minutes at the default one-request-per-second pace per registry, plus a few seconds of compute at 128 MB. The IANA bootstrap file is cached for 24 hours, so repeat runs do not download it again the same day.

### Input

See the input tab for every field. `domains` is the list of names you operate. `mode` is `lookup` or `watch`. `watchlistId` selects the saved snapshot. `delayMsPerRegistry` defaults to 1000 ms and cannot go below 200 ms. `maxConcurrencyPerRegistry` is fixed at 1.

### Output

You can download the dataset in JSON, HTML, CSV or Excel.

Lookup row:

```json
{
  "kind": "lookup",
  "domain": "example.com",
  "tld": "com",
  "registered": true,
  "rdapServer": "https://rdap.verisign.com/com/v1/",
  "registrar": "Example Registrar",
  "registrarIanaId": "292",
  "createdAt": "1997-09-15T04:00:00Z",
  "expiresAt": "2028-09-14T04:00:00Z",
  "updatedAt": "2019-09-09T15:39:04Z",
  "daysToExpiry": 700,
  "statuses": ["client transfer prohibited"],
  "nameservers": ["ns1.example.com", "ns2.example.com"],
  "dnssecSigned": false,
  "supportLevel": "full",
  "error": null
}
```

Watch alert (only the changed field):

```json
{
  "kind": "change",
  "domain": "example.com",
  "changeType": "NAMESERVER_CHANGE",
  "field": "nameservers",
  "before": ["ns1.example.com"],
  "after": ["ns1.example.net", "ns2.example.net"]
}
```

Watch state is a single gzipped record per watchlist in the named store `rdap-watch-state`. Each domain is stored as a hash of the watched fields. The record also keeps those compact fields so the next run can show `before` and `after`. The `changed` object in that record lists full values only for fields that changed on the last run. The bootstrap file is the other record in that store and is refreshed at most once a day.

### Registry support

| Ending | Support | Server |
|---|---|---|
| `.com`, `.net` | full | Verisign, from the IANA bootstrap |
| `.org` | full | Public Interest Registry |
| `.uk` (including `example.co.uk`) | full | Nominet |
| `.nl` | full, but `expiresAt` is null | SIDN |
| `.co`, `.us` | full | Hand-added (`rdap.registry.co`, `rdap.nic.us`) |
| `.de`, `.ch` | limited | Hand-added. `.de` has no registrar or expiry. `.ch` publishes a registration date. |
| `.eu`, `.io`, `.me`, `.be`, `.it`, `.es`, `.at`, `.se`, `.dk`, `.jp`, `.cn`, `.ru` | unsupported in v1 | No request is sent |

Other endings that are absent from the bootstrap and from the hand-added list are also `unsupported`.

### Terms, rate limits and blocked registries

Use this Actor to monitor names you administer. Verisign's RDAP terms say: "under no circumstances will you use the data to: (1) allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising … or (2) enable high volume, automated, electronic processes that send queries or data to the systems of Verisign or an ICANN-accredited registrar" ([Verisign RDAP terms](https://www.verisign.com/legal-center/rdap-terms/)). Public Interest Registry (`.org`) publishes the same restrictions in its RDAP notice. DENIC (`.de`) says the data "may only be used within the scope of technical or administrative necessities of Internet operation or to remedy legal problems. The use for other purposes, in particular for advertising, is not permitted."

The Actor caps a run at 5,000 names, sends one request at a time per registry, and waits at least one second between those requests. It honours `Retry-After` up to 120 seconds (a 62 second wait is waited out). A longer block, including a multi-hour `Retry-After`, marks that registry `source_unavailable` for the rest of the run. The run still finishes successfully. There is no proxy and no attempt to route around a block.

### FAQ, disclaimers and support

This Actor is not affiliated with IANA, ICANN, Verisign, PIR, DENIC, SWITCH, SIDN or any other registry. It reads public RDAP only.

Questions and bugs: use the Issues tab on the Actor page. Programmatic runs use the API tab.

**Do I need an API key?** No. RDAP servers used here are public. Bring the domain names yourself.

**Will it tell me a name is free to register?** No. `registered: false` is only an RDAP 404.

**What if a registry rate-limits the platform IP?** That registry's rows come back as `source_unavailable` and are not charged (once pricing exists). Run again later. The Actor will not try to evade the block.

# Actor input Schema

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

Domain names in your own portfolio, one per line (max 5,000 per run). Example: example.com. This Actor is for names you administer. It is not a lead list or an availability checker.

## `mode` (type: `string`):

lookup returns one row per domain. watch does the same and also emits a change alert when a later run, with the same Watchlist ID, sees expiry coming up or a registrar, nameserver, status or DNSSEC change.

## `expiryWithinDays` (type: `integer`):

In watch mode, emit EXPIRY_SOON once per domain per expiry date when the domain expires within this many days.

## `alertOn` (type: `array`):

Watch-mode alerts to emit: expiry_soon, registrar_change, nameserver_change, status_change, dnssec_change, dropped, newly_registered. now_unregistered is accepted as an alias of dropped. Unknown values are ignored.

## `watchlistId` (type: `string`):

Name of the saved snapshot. Use one ID per portfolio. State is one gzipped record in the named key-value store rdap-watch-state.

## `maxConcurrencyPerRegistry` (type: `integer`):

Kept at 1. Each registry receives one request at a time, then waits Delay ms. Higher values are ignored.

## `delayMsPerRegistry` (type: `integer`):

Pause after each request to a registry before the next request to that same registry. Default 1000 ms. Minimum 200 ms.

## Actor input object example

```json
{
  "domains": [],
  "mode": "lookup",
  "expiryWithinDays": 30,
  "alertOn": [
    "expiry_soon",
    "registrar_change",
    "nameserver_change",
    "status_change",
    "dnssec_change",
    "dropped",
    "newly_registered"
  ],
  "watchlistId": "default",
  "maxConcurrencyPerRegistry": 1,
  "delayMsPerRegistry": 1000
}
```

# Actor output Schema

## `dataset` (type: `string`):

One lookup row per domain. Watch mode adds one row per change.

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

Counts, bootstrap source, blocked registries and charge-limit leftovers

# 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": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("mikee368/domain-rdap-watch").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": [] }

# Run the Actor and wait for it to finish
run = client.actor("mikee368/domain-rdap-watch").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 call mikee368/domain-rdap-watch --silent --output-dataset

```

## MCP server setup

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

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/dhoci7f6lAiA4UgN2/builds/WyaYGrj7pr3XMFegI/openapi.json
