# 🌐 Bulk WHOIS & DNS Lookup - Domain Records API (`renzomacar/whois-dns-lookup`) Actor

Bulk WHOIS + DNS + SSL lookup for a list of domains: registrar, created/expiry dates, domain age, status, nameservers, A/AAAA/MX/TXT/NS/CNAME/SOA and the live TLS certificate. Keyless WhoisXML alternative.

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

## Pricing

$5.00 / 1,000 domain looked ups

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/platform/actors/running/actors-in-store#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

## 🌐 Bulk WHOIS & DNS Lookup — Domain Records API

### 🎥 Video walkthrough

https://youtu.be/8AGkBtdSkvU

Paste a list of domains, get back one clean record per domain with everything you'd normally have to stitch together from three different tools: the **WHOIS / registration** record, the **live DNS zone**, and the **SSL certificate**. No API key, no per-lookup subscription, no scraping fragile HTML — this Actor talks to the same first-party, free data sources the registries and DNS themselves expose.

It's a drop-in, pay-as-you-go alternative to WhoisXML API, DomainTools, and the various "DNS lookup API" services, priced per domain instead of per seat or per monthly quota.

***

### What this Actor tells you

For every domain you pass in, three independent questions get answered in parallel:

#### 1. Who owns it and when does it expire? (WHOIS via RDAP)

WHOIS answers *"who registered this domain, through whom, and for how long?"* — the metadata a domain registry keeps. Instead of parsing the legacy free-text WHOIS protocol (rate-limited, unstructured, different on every TLD), this Actor uses **RDAP** (Registration Data Access Protocol), the modern JSON successor that every gTLD registry now runs. We bootstrap through `rdap.org`, which redirects to the authoritative registry for each domain, so `.com`, `.io`, `.dev`, `.co.uk` and hundreds of others all return structured data.

From RDAP you get:

| Field | Meaning |
|---|---|
| `registrar` | The registrar the domain was registered through (e.g. *MarkMonitor Inc.*, *GoDaddy.com, LLC*). |
| `createdDate` | When the domain was first registered (ISO 8601). |
| `updatedDate` | When the registration record last changed. |
| `expiresDate` | When the registration lapses if not renewed — the field domain-portfolio owners live and die by. |
| `domainAgeYears` | Age in years, derived from `createdDate`. A trust/legitimacy signal. |
| `domainStatus[]` | EPP status codes such as `client transfer prohibited`, `server delete prohibited`, `active`. Tells you if the domain is locked or in a risky state. |
| `nameservers[]` | The delegated nameservers the registry has on file (authoritative). |
| `registrantCountry` | The registrant's country, *when the registry publishes it* (many redact it under GDPR — you'll get `null`, not a crash). |

#### 2. Where does it actually resolve? (live DNS)

WHOIS is what the registry knows; **DNS is what the internet actually does right now**. Using Node's built-in resolver we pull the live zone:

| Record | What it's for |
|---|---|
| `dns.a[]` | IPv4 addresses — where the site is hosted. |
| `dns.aaaa[]` | IPv6 addresses. |
| `dns.mx[]` | Mail exchangers, each with its `priority`. Reveals the email provider (Google Workspace, Microsoft 365, Proofpoint…). |
| `dns.txt[]` | TXT records — SPF, domain-verification tokens, DKIM keys, arbitrary metadata. |
| `dns.ns[]` | The nameservers actually answering for the zone. |
| `dns.cname` | Any CNAME at the apex (rare, but present on some hosted setups). |
| `dns.soa` | Start-of-Authority record: primary nameserver, hostmaster, serial, refresh/retry/expire/minttl. The zone's "heartbeat". |

Every record type is resolved independently and any *no-data* / *not-found* / timeout degrades to an empty array or `null` — a domain that legitimately has no AAAA or no MX gives you an honest empty result, never an error.

#### 3. Is its HTTPS healthy? (live SSL certificate)

A single TLS handshake to `:443` (with SNI set to the domain) reads the **live leaf certificate** — the thing your users' browsers see:

| Field | Meaning |
|---|---|
| `ssl.issuer` | The Certificate Authority (e.g. *Google Trust Services*, *DigiCert Inc*, *Let's Encrypt*). |
| `ssl.validFrom` / `ssl.validTo` | The certificate's validity window (ISO 8601). |
| `ssl.daysUntilExpiry` | Days until the cert expires — the number that pages your on-call at 2 a.m. when it's negative. |
| `ssl.isValid` | Whether the chain validated against the system trust store **and** we're inside the validity window. |

Domains with no HTTPS listener, a self-signed cert, or a handshake failure return a `null` cert with `isValid: false` — gracefully, in one short timeout.

#### Derived convenience flags

| Field | Logic |
|---|---|
| `hasWebsite` | `true` if any A or AAAA record exists. |
| `hasMx` | `true` if the domain publishes MX records (it can receive mail). |
| `isExpiringSoon` | `true` if the **domain registration** *or* the **SSL certificate** expires within 30 days — your single "needs attention" flag. |

***

### Example input

```json
{
  "domains": ["google.com", "github.com", "stripe.com"],
  "maxConcurrency": 5
}
```

You can also paste URLs (`https://github.com/features`) or emails (`jane@stripe.com`) — they're reduced to the bare domain automatically. Duplicates are removed.

### Example output (one record per domain)

```json
{
  "domain": "stripe.com",
  "registrar": "SafeNames Ltd.",
  "createdDate": "1995-09-12T04:00:00.000Z",
  "updatedDate": "2024-08-10T12:00:00.000Z",
  "expiresDate": "2027-09-11T04:00:00.000Z",
  "domainAgeYears": 30.8,
  "domainStatus": ["client transfer prohibited", "server delete prohibited"],
  "nameservers": ["ns-1087.awsdns-07.org", "ns-1882.awsdns-43.co.uk", "ns-423.awsdns-52.com", "ns-705.awsdns-24.net"],
  "registrantCountry": null,
  "dns": {
    "a": ["198.202.176.231", "198.137.150.231"],
    "aaaa": [],
    "mx": [
      { "exchange": "aspmx.l.google.com", "priority": 10 },
      { "exchange": "alt1.aspmx.l.google.com", "priority": 20 }
    ],
    "txt": ["v=spf1 include:_spf.google.com ~all"],
    "ns": ["ns-423.awsdns-52.com", "ns-705.awsdns-24.net"],
    "cname": null,
    "soa": {
      "nsname": "ns-1882.awsdns-43.co.uk",
      "hostmaster": "awsdns-hostmaster.amazon.com",
      "serial": 1, "refresh": 900, "retry": 900, "expire": 1209600, "minttl": 3600
    }
  },
  "ssl": {
    "issuer": "DigiCert Inc",
    "validFrom": "2026-05-27T00:00:00.000Z",
    "validTo": "2026-09-03T23:59:59.000Z",
    "daysUntilExpiry": 62,
    "isValid": true
  },
  "hasWebsite": true,
  "hasMx": true,
  "isExpiringSoon": false,
  "checkedAt": "2026-07-04T14:30:00.000Z"
}
```

***

### What people use it for

- **Domain-portfolio monitoring** — run your whole portfolio on a schedule and watch `expiresDate`, `domainStatus` and `nameservers` for anything that drifted. Catch a domain about to lapse before a squatter does.
- **Expiry alerting** — filter on `isExpiringSoon` (registration *or* SSL within 30 days) and pipe it into Slack, email or a ticket. One flag, both risks.
- **Security & threat recon** — pull nameservers, mail providers, SSL issuers and registration age across a list of look-alike or phishing domains. A domain registered 4 days ago with a free cert and no MX is a very different animal from a 30-year-old domain on MarkMonitor.
- **Lead qualification / enrichment** — attach registrar, domain age, MX provider and "has a real HTTPS site" to a list of prospect domains before your sales team touches them. Old domain + Google Workspace MX + valid EV-grade cert = a real business.
- **Infra audits & migrations** — confirm every domain in an acquisition or migration points at the nameservers, MX and certs you expect.
- **Bulk DNS/WHOIS API replacement** — wire the Actor's API endpoint into your own app and get structured WHOIS + DNS + SSL without running or paying for three separate services.

***

### A WhoisXML / DNS-Lookup-API alternative

Services like **WhoisXML API**, **DomainTools**, and hosted **DNS Lookup APIs** sell WHOIS, DNS and SSL data behind monthly quotas and per-seat pricing, and each usually covers only *one* of the three. This Actor:

- **Combines all three** (WHOIS + DNS + SSL) into one record per domain — one call, not three subscriptions.
- **Charges per domain looked up** (pay-per-event), so a 40-domain check costs cents and a 40,000-domain portfolio scales linearly — no quota you have to size in advance.
- **Uses 100% free, first-party sources** (RDAP registries, live DNS, live TLS), so there's no upstream API key to manage or rotate.
- **Runs on Apify** — schedule it, hit it via API/webhook, export to JSON/CSV/Excel, or chain it into a larger pipeline.

***

### FAQ

**Is RDAP the same as WHOIS?**
RDAP is the modern, structured, JSON replacement for the legacy WHOIS protocol, mandated by ICANN for gTLDs. You get the same registration facts (registrar, dates, status, nameservers) in a clean, reliable format instead of scraping free text. A few ccTLDs don't yet run RDAP; for those you'll get the DNS + SSL data and `null` WHOIS fields.

**Why is `registrantCountry` (or registrant name/email) often null?**
Post-GDPR, most registries redact personal registrant data by default. When a registry does publish the country, you get it; when it doesn't, you get `null` — the Actor never guesses.

**Why might a domain show `hasWebsite: false` but still have a valid SSL cert?**
`hasWebsite` is based on A/AAAA records at the *exact* domain you passed. Some sites only serve on `www.` or route through a CDN CNAME. The SSL check connects to `:443` on the domain and reads whatever cert is presented, so the two can differ — both facts are reported honestly.

**How fresh is the data?**
Every field is fetched live at run time; `checkedAt` timestamps each record. DNS and SSL are real-time; RDAP reflects the registry's current record.

**Will it crash on a dead or malformed domain?**
No. Invalid input, NXDOMAIN, no-HTTPS, RDAP misses and timeouts all degrade to empty arrays / `null` fields. You always get exactly one record per input domain.

**How is it priced?**
Pay-per-event: you're charged once per domain successfully looked up (`domain-looked-up`). Set `ACTOR_MAX_TOTAL_CHARGE_USD` to hard-cap spend on huge lists.

***

### Automate it

- **Schedule** the Actor (daily/weekly) over your domain portfolio and route `isExpiringSoon === true` records to Slack or email — never miss a registration or certificate renewal again.
- **Call it via API/webhook** from your own backend to enrich domains on demand, and read results straight from the dataset as JSON or CSV.
- **Chain it**: feed the output into a spreadsheet, a BI tool, or the next Actor in an Apify task flow.

***

### Related Actors

- **[Domain & Email Deliverability Enricher](https://apify.com/renzomacar/domain-health-enricher)** — goes deeper on the *email* side of the same domains: MX + provider, SPF, DMARC (+ policy), DKIM, and a 0–100 email-security score with low/medium/high deliverability risk. Use it to score domains before cold outreach; use *this* Actor for the WHOIS/DNS/SSL registration picture.
- **Company Enrichment** — turn a domain into firmographics (company name, size, industry, socials). Pair it with this Actor to go from a raw domain list to fully-qualified, verified B2B records.

***

*Built for developers, sales-ops and security teams who need WHOIS + DNS + SSL in bulk, priced by the domain instead of by the seat.*

# Actor input Schema

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

One domain per line (e.g. google.com). URLs (https://google.com/search) and emails (jane@google.com) are accepted too — they're reduced to the bare domain automatically. Each domain is looked up for: WHOIS/RDAP registration data (registrar, created/updated/expiry dates, domain age, status codes, nameservers, registrant country), live DNS records (A, AAAA, MX, TXT, NS, CNAME, SOA), and its live SSL/TLS certificate (issuer, validity window, days-until-expiry). One clean record is returned per domain.

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

How many domains to look up in parallel. Higher = faster on big lists; the default is a friendly value for public DNS resolvers, RDAP and TLS endpoints.

## Actor input object example

```json
{
  "domains": [
    "google.com",
    "github.com",
    "stripe.com"
  ],
  "maxConcurrency": 5
}
```

# Actor output Schema

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

Rows produced by Bulk WHOIS & DNS Lookup, one item per result in the default dataset.

# 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",
        "github.com",
        "stripe.com"
    ],
    "maxConcurrency": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("renzomacar/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": [
        "google.com",
        "github.com",
        "stripe.com",
    ],
    "maxConcurrency": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("renzomacar/whois-dns-lookup").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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",
    "github.com",
    "stripe.com"
  ],
  "maxConcurrency": 5
}' |
apify call renzomacar/whois-dns-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=renzomacar/whois-dns-lookup",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/ID6zjaerMgcgYb052/builds/XxyJnmxEbccBYVaWz/openapi.json
