# Domain WHOIS Lookup - Bulk RDAP, Expiry, DNS & Availability (`rjmacarthy/domain-whois-lookup`) Actor

Bulk domain lookup: registrar, creation and expiry dates, EPP statuses, nameservers and availability via official RDAP (WHOIS fallback), plus DNS, SPF and DMARC. Change monitoring for portfolios. No personal data. $0.002 per domain.

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

## Pricing

from $2.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/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 - Bulk RDAP, Expiry Dates, DNS & Availability

Look up hundreds of domains at once and get, for each one, the **registrar, creation date, expiry date and days left, EPP statuses, nameservers, DNSSEC and availability** from the official registry **RDAP** service (with WHOIS port-43 fallback for ccTLDs that have no RDAP), plus its **DNS records with SPF and DMARC**. Everything for one domain lands in one clean JSON row. Other WHOIS Actors give you either availability or raw WHOIS text, and some add a start fee. This one returns parsed registration data, availability and email-authentication records together for **$0.002 per domain with no start fee**. It also has a **change-monitoring mode** that only reports domains whose registrar, expiry, status or nameservers changed since the last run. No personal data: names, emails, phone numbers and street addresses are never output.

### What data can you get?

| Field | Example |
|---|---|
| Registered / likely available | `registered: true`, `available: false` |
| Registrar, IANA ID, registrar website | `Amazon Registrar, Inc.`, `468` |
| Created, updated, expires (ISO dates) | `2035-06-02T17:14:10.000Z` |
| Days until expiry, domain age in days | `3128`, `6367` |
| EPP statuses | `clientTransferProhibited`, `serverHold`, `redemptionPeriod` |
| Nameservers, DNSSEC | `ns-449.awsdns-56.com`, `true` |
| Registrant organisation and country (when public, never person names) | `Shopify Inc.`, `CA` |
| DNS: A, AAAA, MX (with priority), NS, TXT, www CNAME | `aspmx.l.google.com` (10) |
| SPF and DMARC records | `v=DMARC1; p=reject; ...` |
| Source and errors | `rdap`, `whois`, `dns-only` |
| Changes since last run (monitoring mode) | `[{ field: "expiresAt", previous, current }]` |

### Use cases

- **Domain investors:** check availability and expiry dates for a list of names, find domains that are about to expire or are in `redemptionPeriod` / `pendingDelete`.
- **Portfolio and brand-protection teams:** schedule a daily run over all your domains and look-alikes; get alerted when a registrar, expiry date, status or nameserver changes, or when a typo-domain gets registered.
- **SEO and agency due diligence:** domain age, registrar and hosting (A/NS records) for a list of sites before you buy links or a website.
- **Security analysts:** newly registered or recently changed domains, DNSSEC, SPF and DMARC posture for phishing and vendor-risk checks.
- **Sales and RevOps:** enrich a list of company websites with registrant organisation, mail provider (MX) and email-auth status.

### How to use

1. Paste domains or URLs into **Domains**, one per line. URLs and subdomains are reduced to the registrable domain (`https://www.shop.example.co.uk/page` -> `example.co.uk`). Duplicates are removed.
2. Keep **Include DNS records** on if you want A/MX/TXT/SPF/DMARC; turn it off for a pure WHOIS/expiry check.
3. Click **Start** and download the results as JSON, CSV or Excel, or read them through the API.

#### Input examples

Check a domain portfolio's expiry dates:

```json
{
    "domains": ["shopify.com", "shopify.ca", "shopify.de", "shopify.fr", "shopify.io"],
    "includeDns": false
}
```

Check whether brand names are free:

```json
{
    "domains": ["brewlytics.com", "brewlytics.io", "brewlytics.ai", "brewlytics.co", "getbrewlytics.com"],
    "includeDns": false
}
```

SPF / DMARC audit:

```json
{
    "domains": ["https://mailchimp.com/pricing", "www.hubspot.com", "salesforce.com", "zendesk.com", "atlassian.com"],
    "includeDns": true
}
```

Daily change monitoring (schedule it):

```json
{
    "domains": ["yourbrand.com", "yourbrand.net", "yourbrand.co.uk", "yourbrnad.com"],
    "onlyChanges": true
}
```

### Output example

Real output for `apify.com` (TXT list trimmed):

```json
{
  "id": "apify.com",
  "domain": "apify.com",
  "tld": "com",
  "url": "https://rdap.verisign.com/com/v1/domain/apify.com",
  "registered": true,
  "available": false,
  "registrar": "Amazon Registrar, Inc.",
  "registrarIanaId": "468",
  "registrarUrl": "http://registrar.amazon.com",
  "createdAt": "2009-06-02T17:14:10.000Z",
  "updatedAt": "2026-05-16T16:53:04.000Z",
  "expiresAt": "2035-06-02T17:14:10.000Z",
  "daysUntilExpiry": 3128,
  "domainAgeDays": 6367,
  "statuses": ["clientTransferProhibited"],
  "nameservers": ["ns-1225.awsdns-25.org", "ns-1928.awsdns-49.co.uk", "ns-449.awsdns-56.com", "ns-839.awsdns-40.net"],
  "dnssec": true,
  "registrantOrganization": null,
  "registrantCountry": "US",
  "dns": {
    "a": ["99.86.109.13", "99.86.109.15", "99.86.109.73", "99.86.109.97"],
    "aaaa": ["2600:9000:223c:4400:9:a03e:6540:93a1"],
    "mx": [{ "exchange": "aspmx.l.google.com", "priority": 1 }, { "exchange": "alt1.aspmx.l.google.com", "priority": 5 }],
    "ns": ["ns-1225.awsdns-25.org", "ns-1928.awsdns-49.co.uk", "ns-449.awsdns-56.com", "ns-839.awsdns-40.net"],
    "txt": ["v=spf1 a mx include:_spf.google.com include:mailgun.org include:amazonses.com include:19497222.spf05.hubspotemail.net -all"],
    "spf": "v=spf1 a mx include:_spf.google.com include:mailgun.org include:amazonses.com include:19497222.spf05.hubspotemail.net -all",
    "dmarc": "v=DMARC1; p=reject; sp=reject; pct=100; rua=mailto:***@apify.com; ri=604800",
    "wwwCname": null,
    "error": null
  },
  "source": "rdap",
  "error": null,
  "changes": null,
  "scrapedAt": "2026-11-08T12:26:56.425Z"
}
```

An unregistered domain comes back with `registered: false`, `available: true` and empty registration fields.

### Pricing

Pay per event, no start fee:

| Event | Price |
|---|---|
| `domain`: one domain looked up and output | $0.002 |

- **1,000 domains ≈ $2.00**, 100 domains ≈ $0.20.
- In monitoring mode you only pay for domains that changed (and for every domain on the first run, which saves the baseline).
- Domains where nothing at all could be looked up (no RDAP, no WHOIS, no DNS) are output with an `error` but **not charged**.
- Set **Maximum cost per run** to cap spending; the Actor stops cleanly at the limit.

### How it works

1. Each domain's registry RDAP server is found in the official IANA bootstrap file. The registry answers with registrar, dates, statuses, nameservers and DNSSEC. For thin registries such as .com, the registrar's own RDAP service is also queried for the registrant organisation and country.
2. If a TLD has no RDAP service (for example .de, .io, .co, .eu, .ch, .it, .es, .jp) or the RDAP server refuses or rate-limits the request, the registry's WHOIS server (port 43) is used and the common fields are parsed (`source: "whois"`).
3. If both fail, the item is still output with DNS data (`source: "dns-only"`) and the reason in `error`. Domains with nameservers in DNS are marked `registered: true`.
4. DNS is resolved with 1.1.1.1 and 8.8.8.8 by default (configurable), with the system resolver as fallback.

### Tips and limits

- **"Available" means likely available.** A registry with no record usually means the name can be registered, but reserved, premium or blocked names can also return no record. Check with a registrar before you count on it.
- **WHOIS fallback is best effort.** WHOIS formats differ per registry. Some registries publish very little: DENIC (.de) only gives nameservers, status and the last-changed date, no registrar or expiry date.
- **Rate limits.** Registries throttle per IP. The Actor makes at most 3 parallel requests per RDAP server and backs off on HTTP 429. Some registries (e.g. Nominet for .uk) have a daily quota per IP; once it is used up the Actor switches to WHOIS for that registry. Very large lists of a single ccTLD can therefore be slower or end up with `source: "dns-only"` for part of the list.
- **Registrant organisation is often empty.** Since GDPR most registrars redact registrant data. Privacy-service names (e.g. "Domains By Proxy", "c/o whoisproxy.com") are treated as redacted.
- **Email addresses are masked** in TXT and DMARC records (`rua=mailto:***@example.com`), so you still see the reporting provider's domain.
- **Proxy:** not needed normally. It only applies to RDAP (HTTP) requests; DNS and WHOIS port 43 don't go through a proxy. Residential proxy is not supported (it would cost more than the lookup); selecting it falls back to Apify datacenter proxy with a warning.
- **Max domains** defaults to 1,000 per run (0 = no limit).

### FAQ

**Is it legal to look up WHOIS / RDAP data?**
RDAP and WHOIS are public services run by the registries for exactly this purpose. This Actor only outputs registration facts (registrar, dates, statuses, nameservers), organisation and country fields that the registry publishes, and DNS records. It never outputs names of people, email addresses, phone numbers or street addresses. You are responsible for using the data in line with the registries' terms and the laws that apply to you (e.g. no spam).

**Why is `source` "whois" for some domains?**
Their TLD has no RDAP service, or the RDAP server rate-limited the request. WHOIS gives the same core fields for most registries, but some fields may be null.

**How does change monitoring work?**
With **Only output changes** on, the Actor stores each domain's registration state, registrar, expiry date, statuses and nameservers in a key-value store named `domain-whois-lookup-state` in your account. The first run outputs everything (`changes: []`). Later runs only output domains where something changed, with `changes: [{ field, previous, current }]`. A lookup that fails is never reported as a change. Schedule the Actor daily or weekly and connect a webhook or Slack integration to get alerts.

**Why is `daysUntilExpiry` negative?**
The registry's expiry date has passed; the domain is usually in its grace or redemption period (see `statuses`).

**Can I look up subdomains or URLs?**
Yes, they are reduced to the registrable domain because that's what is registered.

**Do I need a proxy or an account?**
No.

### Integrations

- **API:**

  ```bash
  curl -X POST "https://api.apify.com/v2/acts/rjmacarthy~domain-whois-lookup/run-sync-get-dataset-items?token=YOUR_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"domains":["apify.com","example.org"],"includeDns":true}'
  ```

- **Make, Zapier, n8n:** send expiring or changed domains to Google Sheets, Slack, email or your CRM.

- **Webhooks:** trigger your pipeline when a monitoring run finishes.

- **AI agents (MCP):** use the Actor as a tool through the Apify MCP server, e.g. "check whether these 20 names are available as .com".

### Changelog

- **0.1** (2026-11-08): first release. RDAP with IANA bootstrap and registrar follow-up, WHOIS port-43 fallback, DNS with SPF/DMARC, availability, change monitoring.

### Ready-to-run examples

- [Check expiry dates for Shopify's domain portfolio](https://apify.com/rjmacarthy/domain-whois-lookup/examples/shopify-domain-portfolio-expiry-check)
- [Check if a brand name is free as .com, .io, .ai and .co](https://apify.com/rjmacarthy/domain-whois-lookup/examples/brand-name-domain-availability-check)

### More scrapers by the same developer

- [Keyword Suggestions Scraper - Google, YouTube & Bing](https://apify.com/rjmacarthy/keyword-suggestions-scraper)
- [Website Screenshot Generator: Full Page, Mobile & PDF](https://apify.com/rjmacarthy/website-screenshot-generator)
- [AI Visibility Tracker: ChatGPT, Perplexity, Gemini, Claude](https://apify.com/rjmacarthy/ai-search-visibility-tracker)
- [Apple App Store Scraper: Apps, Search & Reviews](https://apify.com/rjmacarthy/app-store-scraper)
- [Bing Search Scraper: Organic Results, Ads & Related Searches](https://apify.com/rjmacarthy/bing-search-scraper)
- [ESPN Scraper - Scores, Odds, Standings & Tennis](https://apify.com/rjmacarthy/espn-scraper)

Browse all my Actors on [my Apify Store profile](https://apify.com/rjmacarthy).

### Support

Found a TLD that doesn't parse well, or need an extra field? Open an issue on the **Issues** tab with the domain. Fixes are usually quick.

# Actor input Schema

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

Domains or URLs, one per line, e.g. "apify.com", "https://www.bbc.co.uk/news". URLs and subdomains are reduced to the registrable domain (www.shop.example.co.uk -> example.co.uk); duplicates are removed.

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

Also resolve A, AAAA, MX, NS and TXT records, the SPF record, the DMARC record (\_dmarc) and the CNAME of www. Email addresses inside TXT/DMARC records are masked.

## `checkAvailability` (type: `boolean`):

Fill the "available" field: true when the registry has no record of the domain (likely available to register). When off, "available" is always null.

## `onlyChanges` (type: `boolean`):

Remember each domain's registrar, expiry date, statuses, nameservers and registration state between runs and only output (and charge for) domains where one of them changed, with a "changes" list. The first run outputs every domain as the baseline. Ideal for scheduled portfolio or brand-protection monitoring.

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

Look up at most this many domains per run (after removing duplicates). 0 = no limit.

## `dnsServers` (type: `array`):

Resolver IP addresses used for DNS records, tried in order; the system resolver is used as a fallback.

## `proxyConfiguration` (type: `object`):

Not needed normally. Applies to RDAP (HTTP) requests only; DNS and WHOIS (port 43) don't use the proxy. Only Apify datacenter proxy or your own proxy URLs are supported: if you select the RESIDENTIAL group, the Actor logs a warning and uses Apify datacenter proxy instead.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "github.com",
    "wikipedia.org",
    "stripe.com",
    "bbc.co.uk",
    "this-domain-should-be-free-7f3k2.com"
  ],
  "includeDns": true,
  "checkAvailability": true,
  "onlyChanges": false,
  "maxItems": 1000,
  "dnsServers": [
    "1.1.1.1",
    "8.8.8.8"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

Registrar, creation and expiry dates, days until expiry, availability and source per domain.

## `dns` (type: `string`):

Nameservers, DNSSEC, A/MX records, SPF and DMARC per domain.

## `all` (type: `string`):

Every field for each domain.

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

Counts per source, request statistics and per-domain errors.

# 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",
        "stripe.com",
        "bbc.co.uk",
        "this-domain-should-be-free-7f3k2.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("rjmacarthy/domain-whois-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",
        "stripe.com",
        "bbc.co.uk",
        "this-domain-should-be-free-7f3k2.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("rjmacarthy/domain-whois-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",
    "stripe.com",
    "bbc.co.uk",
    "this-domain-should-be-free-7f3k2.com"
  ]
}' |
apify call rjmacarthy/domain-whois-lookup --silent --output-dataset

```

## MCP server setup

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