# Bulk Domain Checker: WHOIS (RDAP), DNS, DMARC, SSL & Expiry (`latamdatatools/domain-intelligence-bulk`) Actor

Check many domains at once: registrar, creation and expiry dates (RDAP), DNS records, SPF/DKIM/DMARC email health score, SSL certificate issuer and expiry, HTTPS redirects and domain availability. Expiry alert mode. Public protocols only, no API keys.

- **URL**: https://apify.com/latamdatatools/domain-intelligence-bulk.md
- **Developed by:** [Julian Genuario](https://apify.com/latamdatatools) (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 $3.00 / 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.

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

## Bulk Domain Checker: WHOIS (RDAP), DNS, DMARC, SSL & Expiry

Paste a list of domains (or URLs) and get **one clean row per domain** with everything you usually check in five different tools:

- **Registration (RDAP)**: registrar, creation date, **expiry date and days left**, status codes, nameservers, DNSSEC.
- **DNS records**: A, AAAA, NS, MX, TXT, CAA, SOA (plus CNAME/A of the subdomain you entered).
- **Email security**: SPF (qualifier, DNS lookup count), DMARC (policy, pct, reporting), DKIM on common selectors, MTA-STS, BIMI, the mail provider (Google Workspace, Microsoft 365, Zoho...) and an **email health score 0-100 with grade A-F** plus a plain-English list of issues.
- **SSL certificate**: issuer, validity dates, **days until expiry**, SANs, trusted or not (and why), hostname match, TLS version, key type, chain.
- **HTTP**: redirect chain, final URL, whether HTTP redirects to HTTPS, HSTS.
- **Availability**: registered, available or likely available.

It only uses **public, standard protocols**: RDAP (the official successor of WHOIS, discovered through the IANA bootstrap), DNS over HTTPS, a direct TLS handshake on port 443 and a plain HTTP request. No API keys, no scraping of WHOIS websites, no proxies.

### What does Bulk Domain Checker do?

Bulk Domain Checker looks up a list of domains with public protocols: WHOIS data via RDAP (registrar, creation and expiry dates), DNS records (A, AAAA, MX, NS, TXT), SPF / DKIM / DMARC email health score, SSL certificate issuer and expiry, HTTPS redirects and domain availability. Use it for domain portfolio expiry monitoring, email deliverability checks and lead qualification.

### How to use Bulk Domain Checker

1. Click **Try for free** on this page (a free Apify account is enough to start).
2. Fill in the input (see the examples below) or keep the prefilled example.
3. Click **Start** and wait for the run to finish.
4. Download the results from the **Output** tab as JSON, CSV, Excel or HTML, or read them with the Apify API.
5. Optional: create a **Schedule** to run it daily or weekly, and connect a webhook or an integration (Make, Zapier, n8n, Slack).

### Who is it for?

- **Audit your own domains**: IT teams, agencies and MSPs with dozens or hundreds of client domains. See in one table which ones expire soon, have no DMARC, or serve an untrusted certificate.
- **Expiry monitoring**: schedule it weekly with *Only report domains or certificates expiring within N days* and get an email or Slack alert before a domain or SSL certificate lapses.
- **Cold email and deliverability**: check SPF, DKIM and DMARC of your sending domains (and your clients') before a campaign. The score and the issues list tell you exactly what to fix.
- **Due diligence**: domain age, registrar, renewal date, DNSSEC, mail provider and certificate issuer of a company you are about to buy, partner with or invest in.
- **Technical lead enrichment**: enrich a list of company websites with mail provider (Google vs Microsoft), DNS host, email security maturity and certificate authority, for segmentation and outreach.
- **Domainers**: bulk availability check with the registry's own data.

### Input

| Field | What it does |
|---|---|
| **Domains or URLs** | `example.com`, `www.example.com`, `https://example.com/page`, even `info@example.com`. Subdomains are kept for SSL and HTTP; registration, DNS and email use the registrable domain (`shop.example.co.uk` becomes `example.co.uk`). Internationalized names are converted to punycode. Duplicates are removed. |
| **Checks to run** | Any combination of `rdap`, `dns`, `email`, `ssl`, `http`, `availability`. Default: all. |
| **Only report domains or certificates expiring within N days** | Alert mode. `0` = report every domain. |
| **DKIM selectors to try** | 18 common selectors by default (Google, Microsoft 365, Mailchimp/Mandrill, Zoho, Proton, Fastmail, Mailjet...). Add your own. |
| **Include raw TXT records**, **Domains in parallel**, **Timeout**, **Retries** | Tuning. Defaults work for most lists. |

#### Example input

```json
{
  "domains": ["github.com", "https://www.nic.ar/es/", "expired.badssl.com", "my-new-startup-name.com"],
  "checks": ["rdap", "dns", "email", "ssl", "http", "availability"],
  "expiringWithinDays": 0
}
```

### Output (one row per domain)

```json
{
  "input": "github.com",
  "domain": "github.com",
  "host": "github.com",
  "tld": "com",
  "status": "ok",
  "availability": { "status": "registered", "method": "rdap" },
  "registration": {
    "registrar": "MarkMonitor Inc.", "registrarIanaId": "292",
    "createdAt": "2007-10-09T18:20:50.000Z", "expiresAt": "2028-10-09T18:20:50.000Z", "daysUntilExpiry": 713,
    "status": ["client delete prohibited", "client transfer prohibited", "client update prohibited"],
    "nameservers": ["dns1.p08.nsone.net", "ns-421.awsdns-52.com", "..."], "dnssec": false, "rdapServer": "rdap.verisign.com"
  },
  "dns": {
    "a": ["4.228.31.150"], "aaaa": [], "ns": ["..."],
    "mx": [{ "priority": 0, "exchange": "github-com.mail.protection.outlook.com" }],
    "txt": ["..."], "caa": ["0 issue \"digicert.com\"", "..."],
    "soa": { "primaryNs": "dns1.p08.nsone.net", "hostmaster": "hostmaster.nsone.net", "serial": 1656468023 }, "host": null
  },
  "email": {
    "score": 90, "grade": "A", "issues": [],
    "mx": ["github-com.mail.protection.outlook.com"], "mailProvider": "Microsoft 365",
    "spf": { "present": true, "allQualifier": "~all", "lookupCount": 8, "multipleRecords": false, "record": "v=spf1 ... ~all", "includes": ["spf.protection.outlook.com", "..."] },
    "dmarc": { "present": true, "policy": "quarantine", "subdomainPolicy": "reject", "pct": 100, "rua": ["mailto:dmarc@github.com"], "record": "v=DMARC1; p=quarantine; ..." },
    "dkim": { "checked": ["google", "selector1", "..."], "found": [{ "selector": "google", "keyType": "rsa", "revoked": false }] },
    "dkimSelectorsFound": ["google", "selector1", "k1", "k2", "s1", "s2"], "mtaSts": false, "bimi": false
  },
  "ssl": {
    "host": "github.com", "trusted": true, "trustError": null, "hostnameMatch": true, "protocol": "TLSv1.3",
    "subject": "github.com", "issuer": "Sectigo Public Server Authentication CA DV E36", "issuerOrg": "Sectigo Limited",
    "validFrom": "2026-09-01T00:00:00.000Z", "validTo": "2026-11-29T23:59:59.000Z", "daysUntilExpiry": 34,
    "sans": ["github.com", "www.github.com"], "keyType": "EC prime256v1", "chain": ["...", "USERTrust ECC Certification Authority"], "error": null
  },
  "http": { "startUrl": "http://github.com/", "finalUrl": "https://github.com/", "finalStatus": 200, "redirects": 1, "httpsRedirect": true, "hsts": true, "server": "github.com", "error": null },
  "alerts": [],
  "errors": [],
  "checkedAt": "2026-10-26T19:12:22.244Z"
}
```

`status` is `ok`, `not-registered` (only availability is filled), `invalid` (bad input, free) or `error` (every check failed, free). Sections you did not select are `null`. `alerts` lists what needs attention (expiry within 30 days or your N, expired or untrusted certificate, name mismatch, redemption period). `errors` explains missing data (for example a ccTLD without RDAP).

The dataset has three views: **Overview**, **Email security** and **Expiry and SSL**. A `RUN_SUMMARY` record in the key-value store has totals: available domains, expiring domains and certificates, email grade distribution and network statistics.

#### Email health score

| Points | Rule |
|---|---|
| 25 | SPF record present |
| +10 | SPF ends in `-all` or `~all` |
| 25 | DMARC record present |
| +25 / +15 / +5 | DMARC policy `reject` / `quarantine` / `none` |
| +5 | DMARC aggregate reports (`rua`) configured |
| +10 | DKIM key found on a common selector |
| -15 / -25 | multiple SPF or DMARC records, or more than 10 SPF lookups / SPF `+all` |

Grades: A from 90, B from 75, C from 55, D from 35, F below. DKIM selectors cannot be listed through DNS, so a domain that signs with a custom selector can miss those 10 points: add the selector in the input if you know it.

### Expiry monitoring (alert mode)

1. Paste all your domains and set **Only report domains or certificates expiring within N days** to e.g. `30`.
2. Create an **Apify Schedule** (weekly or daily) and an integration (email, Slack, Google Sheets, Make, Zapier, webhook).
3. Most weeks the dataset is empty; when something is about to expire, you get a row with the exact date and days left.

### Availability

- If the TLD has an RDAP service (all gTLDs such as .com, .net, .org, .io, .app and many ccTLDs such as .uk, .ar, .fr, .nl, .br), the registry answers directly: **registered** or **available**. Premium or reserved names can still be unavailable to register.
- If it has no RDAP service (for example .de, .us, .co.jp, .so), the Actor uses DNS: a name that does not exist in DNS is reported as **likely-available**. Confirm with a registrar before buying.

### Privacy

Registration data is limited to **registrar, dates, status codes, nameservers and DNSSEC**. The Actor never reads or stores registrant, administrative, technical or abuse contacts (names, emails, phone numbers, addresses), even when a registry publishes them. Only the domains you provide are checked.

### Pricing

Pay per event: **USD 3 per 1,000 domains checked** (USD 0.003 per domain), whatever checks you select. Invalid inputs and domains where every check failed are free. In alert mode every checked domain counts, also the ones that are not reported. Apify platform usage is included. The run respects your *max charge per run*: it never checks more domains than you will pay for.

### Limits and good behaviour

- RDAP servers are rate-limited per registry (one request every 0.4 s per server, `Retry-After` respected, retries with back-off on 429, 5xx and timeouts).
- DNS goes through DNS over HTTPS (Cloudflare, then Google) with plain UDP resolvers as fallback.
- One TLS handshake per domain (falls back to `www.` if the bare domain has no HTTPS) and at most 10 HTTP requests to follow redirects; response bodies are not downloaded.
- About 1-2 seconds per domain with the default 8 domains in parallel. Up to 50,000 domains per run.
- If no domain could be checked, the run is marked as **failed** (never a silent empty success).

### Why use Bulk Domain Checker

- **One row per domain with everything:** registrar and dates (RDAP, the official successor of WHOIS), DNS records, SPF / DKIM / DMARC with a 0-100 email score, SSL issuer and expiry, HTTP to HTTPS redirect and availability.
- **One flat price per domain**, whatever checks you select.
- **Expiry alert mode:** `expiringWithinDays` returns only domains or certificates that expire soon, ready for a weekly schedule.
- **No API keys and no personal data:** only the registrar is read from RDAP, never registrant, admin or tech contacts.

### Cost examples

Prices at the current pay-per-event rates (see Pricing). You can set a maximum charge per run in the run options.

| Job | Approximate cost |
|---|---|
| 100 domains | about $0.30 |
| 1,000 domains | about $3.00 |
| Monthly expiry check of 500 domains | about $1.50 |

### Use it from your code, an integration or an AI agent

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("latamdatatools/domain-intelligence-bulk").call(run_input={
    "domains": [
        "apify.com",
        "github.com",
        "example.com"
    ]
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

**JavaScript / Node.js** (`npm install apify-client`):

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('latamdatatools/domain-intelligence-bulk').call({
  "domains": [
    "apify.com",
    "github.com",
    "example.com"
  ]
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

**No code:**

- **Schedules:** run it daily or weekly from Apify Console (Schedules), with the same input saved as a Task.
- **Integrations:** send results to Google Sheets, Slack, email, Make, Zapier, n8n or Airbyte from the Integrations tab, or call your own webhook when a run finishes.
- **AI agents:** add it as a tool in Claude, Cursor, VS Code and other MCP clients through the Apify MCP server (`https://mcp.apify.com`), so the agent can run Bulk Domain Checker and read the results.

### FAQ

**Why is `registration` empty for my .de / .us / .co.jp domain?** Those registries do not offer RDAP (only WHOIS, which this Actor does not scrape). DNS, email, SSL and HTTP data are still there.

**Why do I see `httpPortError`?** The server did not answer on port 80, so the HTTP check went straight to HTTPS.

**Is this a WHOIS scraper?** No. RDAP is the official, structured replacement for WHOIS that ICANN requires from every gTLD registry.

**Can it tell me if a domain is free to register?** It reports `availability`: `available` when the registry's RDAP says the name does not exist, `likely-available` when the TLD has no RDAP and DNS returns NXDOMAIN. Premium or reserved names can still be unavailable at a registrar, so confirm there before buying.

Questions or a missing field? Open an issue in the Issues tab.

# Actor input Schema

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

One per line: example.com, www.example.com, https://example.com/page or even an email address (the domain is extracted). Subdomains are kept for the SSL and HTTP checks; registration, DNS and email checks use the registrable domain. Duplicates are removed.

## `checks` (type: `array`):

Leave all selected for the full report. Fewer checks = faster runs (price per domain is the same).

## `expiringWithinDays` (type: `integer`):

Alert mode for scheduled monitoring: when set (e.g. 30), the dataset only contains domains whose registration or SSL certificate expires within N days or has already expired. 0 = report every domain. Every domain checked is billed, also the ones that are not reported.

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

DKIM keys live at <selector>.\_domainkey.<domain> and selectors cannot be listed, so the Actor tries common ones (Google Workspace, Microsoft 365, Mailchimp/Mandrill, Zoho, Proton, Fastmail, Mailjet, generic). Add your own selector if you know it. Empty list = skip DKIM.

## `includeTxtRecords` (type: `boolean`):

Adds every TXT record of the domain (verification tokens, SPF...) to the DNS section. Turn off for smaller rows.

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

How many domains are checked at the same time. RDAP servers of each registry are additionally rate-limited (one request every 0.4 s per server).

## `requestTimeoutSecs` (type: `integer`):

Applies to each DNS-over-HTTPS query, RDAP request, TLS handshake and HTTP request.

## `maxRetries` (type: `integer`):

Retries on timeouts, HTTP 429 (respecting Retry-After) and 5xx from registry RDAP servers.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "github.com",
    "example.com"
  ],
  "checks": [
    "rdap",
    "dns",
    "email",
    "ssl",
    "http",
    "availability"
  ],
  "expiringWithinDays": 0,
  "dkimSelectors": [
    "google",
    "selector1",
    "selector2",
    "default",
    "k1",
    "k2",
    "s1",
    "s2",
    "dkim",
    "mail",
    "smtp",
    "zoho",
    "mxvault",
    "protonmail",
    "fm1",
    "mandrill",
    "mailjet",
    "everlytic"
  ],
  "includeTxtRecords": true,
  "maxConcurrency": 8,
  "requestTimeoutSecs": 10,
  "maxRetries": 2
}
```

# Actor output Schema

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

No description

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

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,latamdatatools/domain-intelligence-bulk"
        }
    }
}
```

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/E1TXYRwv94VALmzsy/builds/diirSTDQ95l7bK3pV/openapi.json
