# Domain WHOIS & DNS Lookup – RDAP, MX, SPF, DMARC, DKIM (`nordic-data/domain-whois-dns-lookup`) Actor

Bulk domain lookup from domains, URLs or e-mails: official RDAP WHOIS (registrar, creation and expiry dates, status, nameservers, DNSSEC), DNS records (A, AAAA, MX, NS, TXT), e-mail provider, SPF, DMARC and DKIM posture, plus SaaS verification records. $0.0024 per domain.

- **URL**: https://apify.com/nordic-data/domain-whois-dns-lookup.md
- **Developed by:** [Niklas Bergström](https://apify.com/nordic-data) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.40 / 1,000 domain lookups

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 & DNS Lookup – RDAP, MX, SPF, DMARC, DKIM

Bulk **domain WHOIS lookup** and **DNS lookup** in one run. Paste domains, website URLs or e-mail addresses and get one clean record per domain with:

- **WHOIS / registration data** from the official registry **RDAP** servers (the ICANN successor to WHOIS): registrar and IANA ID, creation, update and **expiry dates**, domain age, days until expiry, EPP status codes, nameservers, DNSSEC, abuse contact and – where the registry publishes it – registrant organization and country.
- **DNS records**: A, AAAA, MX, NS, TXT (and CAA on request), resolved over DNS-over-HTTPS.
- **E-mail intelligence**: e-mail provider (Google Workspace, Microsoft 365, Zoho, Proton, Mimecast, Proofpoint …), **SPF** policy and includes, **DMARC** policy and report addresses, **DKIM** selectors found, sending services (SendGrid, Mailchimp, HubSpot, Salesforce …) and a 0–100 e-mail security score.
- **SaaS verification records** found in TXT (Google, Microsoft 365, Atlassian, Stripe, OpenAI, Slack, Zoom, DocuSign …) – a quick tech-stack hint for B2B prospecting.
- **Availability**: unregistered domains come back with `registered: false`.

**Price: $0.0024 per domain.** No start fee, no platform usage charges, invalid inputs and duplicates are free.

### Use cases

- **Lead enrichment / B2B prospecting** – which e-mail provider and SaaS tools does a company use? How old is the domain?
- **Domain portfolio monitoring** – expiry dates, registrar, status locks, DNSSEC across hundreds of domains; schedule it and alert on `daysUntilExpiry`.
- **E-mail deliverability & security audits** – missing SPF, `p=none` DMARC, no DKIM.
- **Fraud and phishing triage** – very young domains, odd registrars, no mail setup.
- **Domain availability checks** – `registered: false`.

### Input

| Field | Default | What it does |
|---|---|---|
| `domains` | – | Domains, URLs, subdomains or e-mail addresses (reduced to the registrable domain, e.g. `https://www.shop.example.co.uk/x` → `example.co.uk`). |
| `includeWhois` | true | RDAP registration data. |
| `followRegistrarRdap` | true | For thin registries (.com/.net) also query the registrar's RDAP server for registrant country/organization and abuse contacts. |
| `includeDns` | true | DNS records + e-mail analysis. |
| `checkDkim` / `dkimSelectors` | true / 7 common selectors | DKIM keys can only be probed by selector name. |
| `includeCaa` | false | CAA records. |
| `includeRawRdap` | false | Full RDAP JSON in `rdapRaw`. |
| `maxDomains` | 0 | Stop after N unique domains (0 = all). |
| `maxConcurrency` | 10 | Domains in parallel. |

```json
{ "domains": ["apify.com", "https://www.spotify.com/se/", "jane@nokia.fi"] }
```

### Output (one item per domain)

```json
{
  "recordType": "domain",
  "input": "https://www.spotify.com/se/",
  "domain": "spotify.com",
  "registered": true,
  "whoisSource": "rdap",
  "registrar": "Abion AB",
  "registrarIanaId": "73",
  "createdDate": "2006-04-23T09:07:50Z",
  "expiresDate": "2030-04-23T09:07:50Z",
  "domainAgeDays": 7494,
  "daysUntilExpiry": 1271,
  "statuses": ["client transfer prohibited", "server delete prohibited"],
  "nameservers": ["dns1.p07.nsone.net", "ns-cloud-a1.googledomains.com"],
  "dnssec": false,
  "registrant": { "organization": "Spotify AB", "country": "SE", "redacted": false },
  "abuseEmail": "abuse@abion.com",
  "dns": { "a": ["35.186.224.24"], "mx": [{ "priority": 1, "host": "aspmx.l.google.com" }], "ns": ["..."], "txt": ["..."] },
  "email": {
    "acceptsMail": true,
    "provider": "Google Workspace",
    "spf": { "allQualifier": "softfail", "includes": ["_spf.google.com", "..."] },
    "dmarc": { "policy": "reject", "reportEmails": ["..."] },
    "dkimSelectorsFound": ["google", "k1", "s1"],
    "sendingServices": ["Google Workspace", "..."],
    "securityScore": 100
  },
  "verifiedServices": ["Microsoft 365", "Apple", "Atlassian", "Meta", "OpenAI"],
  "warnings": [],
  "checkedAt": "2026-10-29T13:40:00Z"
}
```

Errors come back as free items with `recordType: "error"` (`invalid_domain`, `lookup_failed`). Run counts are saved as `RUN_STATS` in the key-value store.

**E-mail security score (0–100):** SPF present 30, SPF ends in `-all`/`~all` 10, DMARC present 20, DMARC `quarantine` 15 / `reject` 25, DKIM selector found 15.

### Pricing

| Event | Price |
|---|---|
| Domain lookup (one unique domain delivered) | $0.0024 |

1,000 domains = $2.40. Unregistered domains are charged (availability is a result); invalid inputs, duplicates and lookups where every source failed are free. Set a maximum charge per run and the Actor stops cleanly before exceeding it.

### Coverage and limits

- RDAP covers all gTLDs (.com, .net, .org, .app, .dev, .ai …) and many ccTLDs (.uk, .fr, .nl, .no, .fi, .pl, .cz, .io, .co …) via the IANA bootstrap registry.
- Some ccTLDs publish no RDAP (e.g. .se, .dk, .eu, .es, .it, .be, .at); .de RDAP terms forbid general use, so it is skipped. For these you get `whoisSource: "not_available"` plus full DNS and e-mail data. Classic port-43 WHOIS is not used.
- Registrant data is often redacted under GDPR; `registrant.redacted` tells you when. Countries are returned as published by the registry (ISO code or name).
- DNS is resolved via Cloudflare DNS-over-HTTPS with Google as fallback.
- RDAP data is subject to each registry's terms of use; use it for lawful purposes (no spam).

### FAQ

**Is this WHOIS?** Yes – RDAP is the official, structured replacement for WHOIS that ICANN made the definitive source for gTLD registration data in January 2025 (port-43 WHOIS was sunset).
**Can I check thousands of domains?** Yes. Per-server rate limits are respected automatically.
**Can I run it on a schedule?** Yes – create a task with your domain list and schedule it; filter on `daysUntilExpiry` or `email.dmarc.policy`.

# Changelog

This Actor's version history is a separate document: https://apify.com/nordic-data/domain-whois-dns-lookup/changelog.md

# Actor input Schema

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

One per line. Accepts plain domains (example.com), full URLs (https://www.example.com/page), subdomains (mail.example.com) and e-mail addresses (jane@example.com). Each is reduced to its registrable domain; duplicates are removed and never charged.

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

Registrar, creation/update/expiry dates, domain age, status codes, nameservers, DNSSEC and (where public) registrant organization and country, from the official registry RDAP server.

## `followRegistrarRdap` (type: `boolean`):

For thin registries such as .com/.net, fetch the registrar's own RDAP record to fill in registrant country/organization and abuse contacts. One extra request per domain.

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

A, AAAA, MX, NS and TXT records plus e-mail provider detection, SPF, DMARC, DKIM and SaaS verification records.

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

Checks the DKIM selectors listed below (DKIM keys cannot be listed, only probed by name).

## `dkimSelectors` (type: `array`):

Max 20. Defaults cover Google Workspace, Microsoft 365 and common mail servers.

## `includeCaa` (type: `boolean`):

Which certificate authorities may issue TLS certificates for the domain.

## `includeRawRdap` (type: `boolean`):

Adds the full registry RDAP response as 'rdapRaw' (large).

## `maxDomains` (type: `integer`):

Stop after this many unique domains (0 = no limit).

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

Domains processed in parallel (1-25). Per-server rate limits are always respected.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "https://www.spotify.com/se/",
    "jane@nokia.fi"
  ],
  "includeWhois": true,
  "followRegistrarRdap": true,
  "includeDns": true,
  "checkDkim": true,
  "dkimSelectors": [
    "google",
    "selector1",
    "selector2",
    "default",
    "k1",
    "s1",
    "mail"
  ],
  "includeCaa": false,
  "includeRawRdap": false,
  "maxDomains": 0,
  "maxConcurrency": 10
}
```

# Actor output Schema

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

All domain records and error items from the default dataset (views: Overview, E-mail security).

## `runStats` (type: `string`):

Counts of domains, unregistered domains, invalid inputs, duplicates, errors and HTTP requests.

# 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.spotify.com/se/",
        "jane@nokia.fi"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("nordic-data/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",
        "https://www.spotify.com/se/",
        "jane@nokia.fi",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("nordic-data/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",
    "https://www.spotify.com/se/",
    "jane@nokia.fi"
  ]
}' |
apify call nordic-data/domain-whois-dns-lookup --silent --output-dataset

```

## MCP server setup

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