# Domain WHOIS & DNS Lookup: Bulk RDAP, Age, Expiry, MX, SPF (`charlesmarkette/domain-whois-dns-lookup-bulk-rdap`) Actor

Bulk domain lookup: registrar, creation and expiry dates, domain age, availability, nameservers from official RDAP (WHOIS fallback), plus DNS: website, MX, email provider (Google Workspace, Microsoft 365), SPF and DMARC. Enrich any lead dataset.

- **URL**: https://apify.com/charlesmarkette/domain-whois-dns-lookup-bulk-rdap.md
- **Developed by:** [Charles Markette](https://apify.com/charlesmarkette) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.38 / 1,000 looked-up domains

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** checks domains in bulk and returns, for each one: the **registrar**, **creation and expiry dates**, **domain age**, **days until expiry**, status codes, **nameservers**, DNSSEC and whether the domain is **registered at all** (a quick availability check). It adds the **DNS and email setup** next to that: A/AAAA records (does it have a website?), MX records, the **email provider** behind them (Google Workspace, Microsoft 365, Zoho…), the **SPF** record and the **DMARC** policy. Paste domains, website URLs or email addresses, or point it at any Apify dataset of leads or companies, and the columns are added next to each item.

### Why this Actor

- **Official sources.** Registration data comes from each registry's **RDAP** service (the modern, structured replacement for WHOIS, listed by IANA). For TLDs without RDAP (for example .io, .co and .de), it falls back to the registry's **WHOIS** server.
- **Polite and reliable.** It sends at most 3 requests at a time to any one registry, retries when a server is busy (honouring `Retry-After`), and reports any failure in an `error` column instead of dropping the row.
- **Privacy-minded.** It returns the registry-level facts (registrar, dates, status, nameservers). It never collects the names, emails, phone numbers or addresses of domain owners.
- **Takes messy input.** `https://www.Shop.example.co.uk/page`, `sales@example.com` and `EXAMPLE.com` all become the registered domain (it understands multi-part suffixes such as .co.uk). With *Look up each domain once*, 500 emails from the same company cost one lookup.

### Use cases

- **Lead qualification:** company age, whether the website is live, and whether they use Google Workspace or Microsoft 365, for every lead a scraper finds.
- **Email deliverability checks:** SPF and DMARC policy for a list of sending or prospect domains.
- **Domain portfolio and expiry monitoring:** schedule a daily run on your domains and filter `daysUntilExpiry` below 30.
- **Availability checks:** `registered: false` means the registry has no record of the domain.
- **Brand protection:** check look-alike domains for registration date, registrar and nameservers.

### How to use it

1. Add **Domains, URLs or emails**, and/or pick a **Dataset to enrich**. For datasets, set **Domain fields** if yours aren't named domain, website, url or email.
2. Choose what to look up: **Registration data** and/or **DNS and email setup** (both are on by default).
3. Run it. Each domain becomes one output row. Export as CSV, Excel or JSON, or chain it with an integration.

**Input example**

```json
{
  "domains": ["apify.com", "https://www.wikipedia.org", "sales@example.co.uk"],
  "includeWhois": true,
  "includeDns": true
}
```

**Output example** (shortened)

```json
{
  "domainInput": "apify.com",
  "domain": "apify.com",
  "tld": "com",
  "registered": true,
  "source": "rdap",
  "registrar": "Amazon Registrar, Inc.",
  "registrarIanaId": "468",
  "createdAt": "2009-06-02T17:14:10Z",
  "expiresAt": "2035-06-02T17:14:10Z",
  "domainAgeDays": 6344,
  "daysUntilExpiry": 3152,
  "status": ["client transfer prohibited"],
  "nameservers": ["ns-1225.awsdns-25.org", "ns-449.awsdns-56.com"],
  "dnssec": true,
  "hasWebsite": true,
  "mxRecords": ["aspmx.l.google.com", "alt1.aspmx.l.google.com"],
  "emailProvider": "Google Workspace",
  "spfRecord": "v=spf1 include:_spf.google.com ~all",
  "dmarcPolicy": "reject",
  "error": null
}
```

Other columns: `updatedAt`, `aRecords`, `aaaaRecords` and `whoisServer` (set when the WHOIS fallback was used). A run summary is saved as `OUTPUT` in the key-value store.

### How much does it cost?

Pay per event: **$0.0025 per looked-up domain** (one output row), plus a **$0.001** run start fee. Platform usage is included. These are free: repeats skipped by *Look up each domain once*, domains left out by *Output only registered domains*, rows for input that isn't a domain, and lookups that failed (`registered: null` with an `error`), so re-running failed rows doesn't cost twice.

**Apify Store discounts:** the price above is for Apify's Free plan. Paid plans pay less per 1,000 looked-up domains: $2.18 on Starter, $1.75 on Scale, $1.38 on Business. The "from" price at the top of the Store page is the Business price.

**Worked examples:**

*Examples use the Free-plan rate. Where an example doesn't add it, each run also has its run-start charge, counted per GB of Actor memory (minimum one; one at the default memory); Apify Proxy (where used) is billed separately.*

- 1,000 company websites: 1,000 × $0.0025 + $0.001 = **$2.501**.
- A daily expiry check of 20 domains: $0.051 per run, about **$1.53 per month**.

If you set a maximum cost per run, the Actor stops cleanly at that limit.

### FAQ

#### Some fields are empty for my .de / .io / .co domain.

Those registries don't run RDAP, and their WHOIS servers publish less. DENIC (.de), for example, shows nameservers and status but no creation or expiry dates. The Actor returns what the registry publishes.

#### What does "registered": null mean?

The registry couldn't be reached or didn't answer clearly (see `error`). Re-run those rows later; registries sometimes rate-limit.

#### Why no owner name or email?

Most registries redact them under GDPR, and this Actor deliberately doesn't collect personal contact data.

#### Is "Self-hosted" or "Other" email provider a problem?

No. "Self-hosted" means the MX records point at the domain itself; "Other" means a provider outside the built-in list.

### Other tools by the same author

| Actor | Use it to |
|---|---|
| [Phone Number Validator & Formatter](https://apify.com/charlesmarkette/phone-number-validator-formatter-bulk-e164) | Validate and format the phone numbers in the same lead list. |
| [Merge & Deduplicate Datasets](https://apify.com/charlesmarkette/merge-deduplicate-datasets-only-new-items) | Keep only new leads between runs before looking them up. |
| [Google Sheets Import & Export](https://apify.com/charlesmarkette/google-sheets-sync) | Send the enriched rows to a Google Sheet. |
| [Domain Availability Checker](https://apify.com/charlesmarkette/domain-availability-checker-bulk-name-ideas) | Check which names are still free to register, in bulk. |
| [Dataset Filter & Transform](https://apify.com/charlesmarkette/dataset-filter-transform-rename-flatten-fields) | Keep only the rows you need, e.g. domains expiring soon. |
| [Bulk URL Status Checker](https://apify.com/charlesmarkette/bulk-url-status-checker-redirects-ssl) | Check that each lead's website actually loads, follows its redirects and has a valid SSL certificate. |
| [Website Contact Info Scraper](https://apify.com/charlesmarkette/website-contact-info-scraper-emails-phones-socials) | Find the published emails, phones and social profiles for each domain's website. |
| [Website Tech Stack Detector](https://apify.com/charlesmarkette/website-tech-stack-detector-shopify-cms-analytics) | Add the store platform, CMS and analytics each domain's website uses. |

RDAP bootstrap data: [IANA](https://data.iana.org/rdap/dns.json). Domain parsing: [tldextract](https://github.com/john-kurkowski/tldextract) with the Public Suffix List.

# Actor input Schema

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

One per line. Domains (example.com), website URLs (https://www.example.com/page) or email addresses (name@example.com) all work: the registered domain is taken from each.

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

Optional. A dataset from a scraper run (leads, company lists, Google Maps places…). For each item, the first field below that holds a domain, URL or email is looked up and the result columns are added next to the item's fields. Use {{resource.defaultDatasetId}} in an integration.

## `domainFields` (type: `array`):

Item fields to take the domain from, in order of preference. Dotted paths work (company.website). Default: domain, website, url, email.

## `keepItemFields` (type: `boolean`):

On: each output row is the original item plus the domain columns. Off: only the domain columns.

## `includeWhois` (type: `boolean`):

Registrar, creation, update and expiry dates, domain age, status, nameservers, DNSSEC and whether the domain is registered at all. Uses the registry's official RDAP service and falls back to WHOIS where a TLD has no RDAP.

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

A and AAAA records (has a website), MX records, the email provider behind them (Google Workspace, Microsoft 365…), SPF record and DMARC policy.

## `dedupe` (type: `boolean`):

Skip repeats of a domain already looked up in this run (for example many emails at the same company). Skipped repeats are free.

## `onlyRegistered` (type: `boolean`):

Leave out domains that are not registered (or could not be checked). Left-out domains are free.

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

How many domains to look up at the same time (1–25). Each registry server still gets at most 3 requests at a time.

## `maxResults` (type: `integer`):

Optional cap on output rows (each row is one domain).

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "https://www.wikipedia.org",
    "github.io"
  ],
  "keepItemFields": true,
  "includeWhois": true,
  "includeDns": true,
  "dedupe": true,
  "onlyRegistered": false,
  "concurrency": 8
}
```

# Actor output Schema

## `items` (type: `string`):

No description

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

No description

# 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",
        "https://www.wikipedia.org",
        "github.io"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("charlesmarkette/domain-whois-dns-lookup-bulk-rdap").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",
        "https://www.wikipedia.org",
        "github.io",
    ] }

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

```

## MCP server setup

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

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/eUPwwa7IO18rkI9KG/builds/pMdW8Z8QxnDdhN1Kd/openapi.json
