# Domain WHOIS Lookup API - RDAP, DNS Records, SSL Expiry (`martin10291/domain-whois-dns-lookup`) Actor

Domain WHOIS lookup API built on official RDAP (IANA bootstrap). For each domain you get registrar, creation and expiry dates, domain age, days to expiry, status codes, nameservers and DNSSEC, plus optional DNS records (A, AAAA, MX, NS, TXT, SPF, DMARC), the SSL certificate (issuer, expiry, SANs) an

- **URL**: https://apify.com/martin10291/domain-whois-dns-lookup.md
- **Developed by:** [Martin Petrov](https://apify.com/martin10291) (community)
- **Categories:** SEO tools, Automation, Integrations
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.05 / 1,000 results

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 WHOIS Lookup API - RDAP, DNS Records, SSL Expiry

**Need to know who registered a domain, when it expires, which nameservers it uses, whether its SSL certificate is about to run out and where it redirects?** Those facts live in four different places (WHOIS/RDAP, DNS, TLS, HTTP). This actor collects them in one call and returns one clean JSON row per domain.

Registration data comes from the **official RDAP registry servers** (found through the IANA bootstrap file), not from scraping WHOIS websites. **No API key, no login, no proxies.** Personal data of registrants is never output: you get the registrar (a company) and technical facts only.

### Who needs it

- **Security and IT teams**: watch your own and your vendors' domains for expiry, nameserver changes and certificates that are about to expire.
- **Sales and lead-ops**: enrich a company list with domain age, registrar, mail provider (MX) and SPF/DMARC setup.
- **Domain investors and SEO tools**: domain age, expiry date and status codes in bulk.
- **AI agents and chatbots**: a single tool call answers "who owns example.com, is it expiring, is the certificate valid?".

### What you get per domain

| Field | Meaning |
|---|---|
| `domain`, `registrar`, `registrarIanaId` | Normalized domain and the registrar company with its IANA ID |
| `createdDate`, `updatedDate`, `expiryDate` | Registration events (ISO 8601, UTC) |
| `domainAgeDays`, `daysToExpiry` | Calculated for you |
| `status`, `nameservers`, `dnssec` | EPP status codes, delegated nameservers, DNSSEC signed or not |
| `rdapSource` | The registry RDAP URL the data came from |
| `dns` (optional) | A, AAAA, MX, NS, TXT, CAA plus parsed `SPF` and `DMARC` |
| `ssl` (optional) | Validity, issuer, `notBefore`, `notAfter`, `daysLeft`, TLS version, SANs |
| `redirect` (optional) | Redirect chain from `http://domain` and the `finalUrl` with status |
| `changes` | Only in only-new mode: what changed since the last run |

A domain that cannot be looked up (not registered, registry down or rate limited) is **not saved and not charged**; the reason is listed in the `RUN_SUMMARY` record (`failed`, `lastError`).

### How to use it (step by step)

1. Click **Try for free**.
2. Paste your domains into **Domains**. The default (apify.com, github.com, wikipedia.org) works immediately.
3. Switch DNS, SSL and redirect checks on or off.
4. Click **Start** and download JSON, CSV or Excel, or call it by API.

### API example (copy and paste)

Replace `YOUR_APIFY_TOKEN` with your token from Apify Console, Settings, Integrations.

```bash
curl -s -X POST "https://api.apify.com/v2/acts/martin10291~domain-whois-dns-lookup/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domains":["apify.com","github.com","wikipedia.org"]}'
```

JavaScript:

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

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('martin10291/domain-whois-dns-lookup').call({
  domains: ['apify.com', 'github.com', 'wikipedia.org'],
  includeDns: true,
  includeSsl: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((d) => `${d.domain}: expires in ${d.daysToExpiry} days, SSL ${d.ssl?.daysLeft} days`));
```

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("martin10291/domain-whois-dns-lookup").call(run_input={
    "domains": ["apify.com", "github.com", "wikipedia.org"],
})
for d in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(d["domain"], d["registrar"], d["daysToExpiry"], (d.get("ssl") or {}).get("daysLeft"))
```

### Use it from AI agents (MCP)

This actor works with the [Apify MCP server](https://mcp.apify.com): add `martin10291/domain-whois-dns-lookup` as a tool and Claude, ChatGPT or any MCP-capable agent can look up domains on demand. The output is flat, documented JSON, so agents do not need to parse WHOIS text.

### Domain availability check

Every row has `registered` and `available`. A name with no registry record (RDAP 404) and no DNS delegation comes back as `"registered": false, "available": true` instead of an error, so you can check a list of name ideas in one run. TLDs whose registry has no RDAP server (for example .io) are still answered through DNS: registered or likely available. Always confirm at a registrar before buying (reserved and premium names exist).

```json
{"domain": "my-new-startup-idea-2026.io", "registered": false, "available": true, "availabilitySource": "no-rdap+dns"}
```

**Expiring soon:** set `expiringWithinDays` (e.g. 30) to keep only domains whose registration ends within that many days.

### Monitoring and alerts

In only-new mode rotating A/AAAA IPs (CDNs, load balancers) are ignored by default; turn on `trackIpChanges` to flag them too.

Turn on **Only new or changed domains** and set a **Monitor ID**. The actor remembers a snapshot per domain (registrar, expiry, status, nameservers, DNSSEC, DNS records, certificate issuer and expiry, final URL) and outputs only domains where something changed, with a `changes` list such as `["nameservers", "ssl.notAfter"]`. The first run reports every domain as `["new"]`. Schedule it daily and connect a webhook, Slack or email. Each result is paid for once.

### Input

| Field | Default | Description |
|---|---|---|
| `domains` | apify.com, github.com, wikipedia.org | Domains or URLs (host is used) |
| `includeDns` | true | DNS records, SPF, DMARC |
| `includeSsl` | true | TLS certificate facts |
| `includeRedirects` | true | Redirect chain and final URL |
| `onlyNew` | false | Output only new or changed domains |
| `monitorId` | default | Separate memory per watch list |
| `maxConcurrency` | 5 | Parallel lookups (1-20) |

### Pricing

Pay per event, about **$1.50 per 1,000 domains**. Failed lookups are free.

### Data sources and limits

- Registration: official RDAP servers from the IANA bootstrap (`data.iana.org/rdap/dns.json`). A few registries do not publish RDAP, or rate-limit heavy use; those domains are reported as failed.
- DNS: live queries through the system resolver. SSL: a normal TLS handshake on port 443. Redirects: plain GET requests with the honest User-Agent `RadarDomainLookup/1.0`.
- No registrant names, emails, phones or addresses are collected or output.
- Use the registered domain (`example.co.uk`), not a subdomain.

### FAQ

**Is this WHOIS?** It is the modern replacement: RDAP is the official JSON protocol that registries run today. Same registration facts, structured.

**Why no owner name or email?** Registries redact it since GDPR, and this actor deliberately never outputs personal data.

**Does it need proxies?** No.

### Use it as an integration (chain it after another actor)

This actor can run automatically after any actor whose results contain a website or domain.

1. Open the source actor in Apify Console and go to the **Integrations** tab.
2. Click **Add integration** -> **Actor**, pick this actor and set **Dataset** to `{{resource.defaultDatasetId}}` (leave `domains` empty). Set your other options and save.
3. When the source run finishes, this actor reads its dataset and processes every row. The field is detected automatically; set **Dataset field** if your column has another name.

You can also start it by hand with `datasetId` (pick a dataset or paste its ID) to process an existing dataset.

Good sources to chain from:

- compass/crawler-google-places (Google Maps Scraper)
- martin10291/local-business-leads-osm
- martin10291/reddit-posts-comments-extractor (domain)
- martin10291/website-tech-stack-detector
- any lead list with a website/domain column

# Actor input Schema

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

Domain names to look up, for example apify.com, github.com, wikipedia.org. URLs are accepted too (the host is used). Unregistered names come back as a row with available: true (domain availability check). Use the registered domain, not a subdomain.

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

Add A, AAAA, MX, NS, TXT, CAA records plus parsed SPF and DMARC policy for each domain.

## `includeSsl` (type: `boolean`):

Connect to port 443 and add the TLS certificate issuer, valid-from and valid-until dates, days left and subject alternative names (SANs).

## `includeRedirects` (type: `boolean`):

Follow HTTP redirects from http://domain and report the chain and the final URL with status code. Uses an honest User-Agent and only GET requests.

## `expiringWithinDays` (type: `integer`):

Keep only domains whose registration expires within this many days (e.g. 30 or 90) - find domains to renew or that may drop soon. 0 = keep all.

## `onlyNew` (type: `boolean`):

For monitoring: output only domains whose registration (registrar, expiry, status, nameservers, DNSSEC), DNS records or SSL certificate changed since the last run with the same Monitor ID. The first run reports every domain as new. Each result is paid for once.

## `monitorId` (type: `string`):

Separate memory for each watch list (used with only-new mode). Use a different ID per list of domains.

## `trackIpChanges` (type: `boolean`):

Off (default): changing A/AAAA IPs are ignored in change detection, because CDNs and load balancers rotate IPs on every run. Turn on to also flag IP changes (DNS hijack watch).

## `maxConcurrency` (type: `integer`):

How many domains are looked up in parallel. Lower it if a registry returns rate-limit errors.

## `datasetId` (type: `string`):

Optional. Pick a dataset (or paste its ID) to read the input from, for example the result of Google Maps Scraper. When this actor runs as an integration, the dataset of the triggering run is used automatically (payload.resource.defaultDatasetId), so you can leave this empty. If set, the values found in the dataset replace the main input list.

## `datasetField` (type: `string`):

Optional. The item field that holds the value, for example website, url or videoUrl (dot paths like author.url work). Empty = detected automatically.

## `datasetMaxItems` (type: `integer`):

Upper limit of unique values taken from the dataset, to keep the cost under control.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "github.com",
    "wikipedia.org"
  ],
  "includeDns": true,
  "includeSsl": true,
  "includeRedirects": true,
  "expiringWithinDays": 0,
  "onlyNew": false,
  "monitorId": "default",
  "trackIpChanges": false,
  "maxConcurrency": 5,
  "datasetMaxItems": 1000
}
```

# Actor output Schema

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

One row per domain: registrar, dates, expiry, nameservers, DNS records, SSL certificate and redirect target.

# 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",
        "github.com",
        "wikipedia.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("martin10291/domain-whois-dns-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",
        "github.com",
        "wikipedia.org",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("martin10291/domain-whois-dns-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",
    "github.com",
    "wikipedia.org"
  ]
}' |
apify call martin10291/domain-whois-dns-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,martin10291/domain-whois-dns-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/mJxB2sv5YHDB1klkx/builds/iMBGFbOb8jgO0GXMT/openapi.json
