# Domain WHOIS (RDAP) & DNS Lookup (`tidyharvest/domain-whois-dns-lookup`) Actor

Bulk domain WHOIS & DNS: registrar, created/expiry dates, age, availability, owner organization and country, MX, email provider, SPF, DMARC, DKIM. Covers .com and country domains (.de, .io, .jp, .ru, .cn...). Built to beat santamaria's domain-whois-dns: more found, half the price.

- **URL**: https://apify.com/tidyharvest/domain-whois-dns-lookup.md
- **Developed by:** [TidyHarvest](https://apify.com/tidyharvest) (community)
- **Categories:** Developer tools, Lead generation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 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 WHOIS & DNS Lookup: Bulk Registrar, Expiry, Owner, MX, SPF, DMARC

Check a list of domains in one run and get one clean row per domain: **registrar, creation and expiry dates, domain age, days until expiry, status codes, nameservers, DNSSEC, registered or available, and the registrant organization and country**, plus live **DNS records (A, AAAA, MX, NS, TXT, CNAME, SOA, CAA)**, the **email provider** behind the domain (Google Workspace, Microsoft 365, Zoho...), **SPF, DMARC policy and DKIM**. Works for generic *and* country-code domains (.com, .io, .ai, .de, .uk, .fr, .jp, .ru, .cn, .it, .eu, .nl, .se, .pl and more). **$1.50 per 1,000 domains, no start fee**, cheaper on paid Apify plans.

Built to beat **santamaria-automations/domain-whois-dns** (the most-used domain WHOIS actor) at half its price. Tested on the same 20 domains across 15 TLDs (2026-10-17):

| | This actor | santamaria-automations/domain-whois-dns |
|---|---|---|
| Price per 1,000 domains | **$1.50**, no start fee | $3.00 + $0.001 per run |
| Registrar or registration date found (19 registered domains) | **16** | 15 |
| Registered / available answered (20 domains) | **20** | 14 (6 left blank) |
| Registrant organization found | **8** | 7 |
| DKIM | counts only selectors with a real key | reported 18 "selectors" on example.com, which publishes only empty (revoked) keys |
| Where they still win | | full raw WHOIS text; registrant org for some registrars that hide it from us (e.g. apple.com) |

### What you can do with it

- **Domain portfolio monitoring:** catch domains that are about to expire, changed registrar or lost DNSSEC.
- **Domain availability checks:** bulk-check name ideas; unregistered domains come back with `available: true`.
- **Email deliverability and security audits:** see which domains lack MX, SPF, DKIM or an enforcing DMARC policy (`dmarcPolicy`: none / quarantine / reject).
- **Lead enrichment and market research:** tag company websites with the owning organization, its country, email provider (Google Workspace vs Microsoft 365), DNS host and domain age.
- **Brand protection and fraud checks:** spot newly registered look-alike domains (low `domainAgeDays`), see who owns them, and find the registrar's abuse contact to report them.

### Input

| Field | What it does |
|---|---|
| `domains` | Domains or URLs, one per line. `https://www.shop.example.co.uk/page` becomes `example.co.uk`. Duplicates are removed. |
| `includeRdap` | Registration (WHOIS) data. On by default. |
| `includeRegistrant` | Registrant organization and country. On by default. |
| `includeDns` | DNS records, email provider, SPF, DNS host. On by default. |
| `recordTypes` | DNS types to fetch (default A, AAAA, MX, NS, TXT). |
| `checkDmarc` | Also read the DMARC policy. On by default. |
| `checkDkim` | Look for DKIM keys under 28 common selectors. Off by default (adds a few seconds per domain). |
| `maxItems` | Hard cap on domains per run (and on cost). Up to 10,000. |

Example:

```json
{ "domains": ["apify.com", "https://www.github.com/about", "my-new-idea-2026.com"], "recordTypes": ["A", "MX", "TXT"] }
```

### Output

One item per domain:

```json
{
  "input": "https://www.github.com/about", "domain": "github.com", "tld": "com",
  "registered": true, "available": false, "registeredSource": "rdap",
  "registrar": "MarkMonitor Inc.", "registrarIanaId": "292", "registrarUrl": "http://www.markmonitor.com",
  "registrarAbuseEmail": "abusecomplaints@markmonitor.com", "registrarAbusePhone": "+1.2086851750",
  "status": ["client delete prohibited", "client transfer prohibited", "client update prohibited"],
  "createdDate": "2007-10-09T18:20:50Z", "updatedDate": "2026-09-07T09:22:52Z", "expiresDate": "2028-10-09T18:20:50Z",
  "domainAgeDays": 6942, "daysUntilExpiry": 728,
  "nameservers": ["dns1.p08.nsone.net", "ns-1283.awsdns-32.org", "..."], "dnssec": false,
  "registrantOrganization": "GitHub, Inc.", "registrantCountry": "US", "registrantRedacted": false,
  "rdapServer": "rdap.verisign.com", "rdapError": null,
  "dnsResolves": true,
  "dns": {
    "A": ["172.182.252.133"], "AAAA": [],
    "MX": [{"priority": 0, "host": "github-com.mail.protection.outlook.com"}],
    "NS": ["dns1.p08.nsone.net", "..."], "TXT": ["v=spf1 ip4:192.30.252.0/22 include:spf.protection.outlook.com ...", "..."]
  },
  "hasMx": true, "emailProvider": "Microsoft 365",
  "spf": "v=spf1 ip4:192.30.252.0/22 include:spf.protection.outlook.com ... ~all", "hasSpf": true,
  "dmarc": "v=DMARC1; p=quarantine; sp=reject; pct=100; ...", "hasDmarc": true, "dmarcPolicy": "quarantine",
  "dnsProvider": "NS1", "dnsError": null,
  "checkedAt": "2026-10-17T07:04:17Z"
}
```

With `checkDkim` on, rows also carry `dkimSelectors` (e.g. `["google", "s1", "s2"]`) and `hasDkim`.

`registeredSource` says how `registered` was decided: `rdap` or `whois` (the registry's own record) or `dns` (the registry publishes no record, but the domain is live in DNS). An unregistered domain returns `registered: false, available: true`. That is a valid result and is charged like any other.

### How to use

1. Paste your domains or URLs into **Domains or URLs**.
2. Pick the DNS record types you need, or turn registration data, owner or DNS off.
3. Run, then download the table as CSV, Excel or JSON, or connect it to Sheets, Slack or Zapier with Apify integrations.

### Pricing

**$1.50 per 1,000 domains** on the Free plan (`domain-checked` event), $1.40 on Bronze, $1.10 on Silver and $0.90 on Gold and above. No start fee. Invalid inputs and domains where every lookup failed are not pushed and not charged; they are listed in the run's `SUMMARY` record. `maxItems` and your run's maximum charge are applied before any lookup.

### Is it legal? Limits and notes

- **Registrant data:** only the registrant **organization and country** are returned, and only when the registry or registrar publishes them. Names, emails, phone numbers and street addresses of people are never returned, and redacted data is never unmasked. Privacy-protected domains show `registrantRedacted: true`, including domains registered through a privacy service (Domains By Proxy, DNStination, Withheld for Privacy...): the service's name and country are not reported as the owner's. Turn `includeRegistrant` off to skip owner data entirely.
- Registry terms of use (for example Verisign's for `.com`/`.net`) allow lawful use only and forbid using the data for unsolicited bulk email, phone or fax marketing. By running this actor you agree to follow the terms of the registries you query.
- Some registries publish less: `.de` and `.ch` give no creation or expiry dates, `.es` and some others publish nothing to the public. For those, `registered` comes from DNS (`registeredSource: "dns"`) and the date fields are empty.
- Some registries and registrars rate-limit lookups. When a lookup is still refused after a pause, the row comes back with `rdapError` set, plus the DNS data.
- Hosting-platform subdomains (`myapp.vercel.app`, `user.github.io`) are looked up at the platform's registered domain (`vercel.app`), since that is what the registry knows.
- `registered: false` comes from the registry's "not found" answer. Premium, reserved or blocked names can still be unavailable to register.
- Speed: about 1 second per domain, five at a time, so roughly 1,000 domains in 5-15 minutes depending on how many share one registry.
- Not affiliated with IANA, ICANN, any domain registry or registrar, or santamaria-automations.

# Actor input Schema

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

One per line. Plain domains (`apify.com`), subdomains (`blog.example.co.uk`) or full URLs (`https://www.github.com/about`). Each is reduced to its registrable domain (`example.co.uk`) and duplicates are removed. Invalid entries are skipped and not charged.

## `includeRdap` (type: `boolean`):

Registrar, IANA registrar ID, registrar abuse contact, status codes, created/updated/expiry dates, domain age, days until expiry, registry nameservers, DNSSEC, and whether the domain is registered or available. Covers country-code domains too (.de, .io, .jp, .ru, .cn, .it, .eu and more).

## `includeRegistrant` (type: `boolean`):

The company or organization that owns the domain, and its country, when the registry or registrar publishes them. Privacy-protected domains return registrantRedacted: true. Names, emails and phone numbers of people are never returned. Turn off to skip.

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

Live DNS records via public DNS-over-HTTPS (Cloudflare, Google as fallback), plus email provider detected from MX, SPF and DNS hosting provider.

## `recordTypes` (type: `array`):

Which DNS record types to fetch for each domain. Fewer types = faster runs.

## `checkDmarc` (type: `boolean`):

Also look up the DMARC policy (TXT record at \_dmarc.<domain>). One extra DNS query per domain.

## `checkDkim` (type: `boolean`):

Look for DKIM keys under 28 common selectors (Google, Microsoft 365, Mailchimp, SendGrid, Zendesk, HubSpot, Amazon SES...). A selector counts only when it publishes a real key. Adds a few seconds per domain.

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

Hard cap on domains checked in this run (also caps your cost). Up to 10,000 per run.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "example.com",
    "github.com"
  ],
  "includeRdap": true,
  "includeRegistrant": true,
  "includeDns": true,
  "recordTypes": [
    "A",
    "AAAA",
    "MX",
    "NS",
    "TXT"
  ],
  "checkDmarc": true,
  "checkDkim": false,
  "maxItems": 1000
}
```

# Actor output Schema

## `domains` (type: `string`):

One item per domain checked.

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

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tidyharvest/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/BKXomqk1b4kTgwJg1/builds/pRBkwljfK3blUAjJz/openapi.json
