# Bulk Domain Lookup: DNS, WHOIS, SPF, DMARC & SSL (`rod_analytics/domain-security-audit`) Actor

Bulk DNS and WHOIS lookup for any list of domains: A, MX, NS, TXT records, registrar and expiry date, email provider (Google Workspace, Microsoft 365, ...), plus SPF, DMARC, DKIM and SSL checks with an A-F grade and fixes. No API key.

- **URL**: https://apify.com/rod_analytics/domain-security-audit.md
- **Developed by:** [Rod Services](https://apify.com/rod_analytics) (community)
- **Categories:** Developer tools, Lead generation, 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 $1.05 / 1,000 domain auditeds

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

**Bulk domain lookup** for any list of domains: **DNS records, WHOIS (registrar and expiry date), email provider, SPF, DMARC, DKIM and SSL** in one row per domain.
Paste 10 or 10,000 domains and get a clean table with an **A-F security grade and fixes** for **$1.50 per 1,000 domains**. No API key, no setup.

### What you get

- **DNS and WHOIS lookup:** A, AAAA, MX, NS, TXT and CAA records, registrar, creation date, expiry date, days to expiry and domain age.
- **Email provider detection:** Google Workspace, Microsoft 365, Zoho, Proton, GoDaddy and 30+ more from MX records, plus the security gateway (Mimecast, Proofpoint, Barracuda, Cisco) when one sits in front.
- **Email and web security check:** SPF, DMARC, DKIM, DNSSEC, MTA-STS, BIMI, SSL certificate expiry, HTTP security headers and the HTTPS redirect, scored A to F with a fix for every finding.

Sample rows from the prefilled run (Overview view):

| Domain     | Grade | Email provider           | Email gateway                      | MX                                     | Registrar                        | Domain expires | SPF  | DMARC      | SSL days left |
| ---------- | ----- | ------------------------ | ---------------------------------- | -------------------------------------- | -------------------------------- | -------------- | ---- | ---------- | ------------- |
| apify.com  | A     | Google Workspace         |                                    | aspmx.l.google.com                     | Amazon Registrar, Inc.           | 2035-06-02     | -all | reject     | 103           |
| github.com | A     | Microsoft 365            |                                    | github-com.mail.protection.outlook.com | MarkMonitor Inc.                 | 2028-10-09     | ~all | quarantine | 55            |
| bbc.co.uk  | B     | Unknown (behind gateway) | Broadcom (Symantec) Email Security | cluster1.eu.messagelabs.com            | British Broadcasting Corporation | 2034-12-13     | ~all | reject     | 110           |

Use it for **lead enrichment** (which companies run Google Workspace or Microsoft 365, which domains expire soon), **email deliverability checks** (SPF, DKIM and DMARC for Gmail and Yahoo sender rules), **SSL certificate expiry monitoring**, **MSP client audits** and **vendor risk checks**. It runs on the Apify platform with API access, scheduling, webhooks and integrations for Make, Zapier, n8n, Google Sheets and Slack.

### Quick start

1. Open the **Input** tab.
2. Paste domains into **Domains**, one per line. `example.com`, `https://www.example.com/page` and `jane@example.com` all work.
3. Press **Start**. The prefilled example (apify.com, github.com, google.com, bbc.co.uk) finishes in a few seconds.
4. Open the **Output** tab and pick a view: **Overview**, **DNS & WHOIS**, **Email security**, **Web and TLS** or **Findings**.
5. Download the results as CSV, Excel, JSON or HTML, or fetch them with the API.

### Input example

Only `domains` is needed. Everything else has a sensible default.

```json
{
    "domains": ["apify.com", "github.com", "google.com", "bbc.co.uk"],
    "includeWhois": true,
    "maxWhoisLookups": 1000,
    "dkimSelectors": ["mandrill"],
    "checks": ["dns", "spf", "dmarc", "dkim", "dnssec", "mtaSts", "bimi", "ssl", "headers", "redirect"],
    "maxConcurrency": 10,
    "timeoutSecs": 10
}
```

| Field             | Default | What it does                                                                                         |
| ----------------- | ------- | ---------------------------------------------------------------------------------------------------- |
| `domains`         |         | Domains, URLs or email addresses. Duplicates and a leading `www.` are removed. IDN names work.       |
| `includeWhois`    | `true`  | Registrar, created, expires, days to expiry and domain age. Free.                                    |
| `maxWhoisLookups` | `1000`  | Safety cap per run (1 to 10,000). Domains past the cap are still audited, only without WHOIS data.   |
| `dkimSelectors`   | `[]`    | Extra DKIM selectors. Always checked: google, selector1, selector2, default, k1, s1, mail, dkim.     |
| `checks`          | all     | Any of dns, spf, dmarc, dkim, dnssec, mtaSts, bimi, ssl, headers, redirect. Only these affect grade. |
| `maxConcurrency`  | `10`    | Domains processed in parallel (1 to 50).                                                             |
| `timeoutSecs`     | `10`    | Timeout per TLS, HTTP or WHOIS connection (3 to 60). A whole domain is capped at 45 s.               |

### Output sample

One item per domain. Shortened:

```json
{
    "domain": "github.com",
    "grade": "A",
    "score": 90,
    "summary": { "critical": 0, "high": 0, "medium": 0, "low": 5, "info": 4 },
    "emailProvider": "Microsoft 365",
    "emailGateway": null,
    "emailProviderSource": "mx",
    "mxHosts": ["github-com.mail.protection.outlook.com"],
    "dns": {
        "a": ["140.82.121.3"],
        "mx": [{ "exchange": "github-com.mail.protection.outlook.com", "priority": 0 }],
        "ns": ["dns1.p08.nsone.net", "dns2.p08.nsone.net", "ns-421.awsdns-52.com"],
        "txt": ["MS=6BF03E6AF5CB689E315FB6199603BABF2C88D805"]
    },
    "whois": {
        "domain": "github.com",
        "registrar": "MarkMonitor Inc.",
        "created": "2007-10-09T18:20:50.000Z",
        "expires": "2028-10-09T18:20:50.000Z",
        "daysToExpiry": 735,
        "domainAgeDays": 6935,
        "status": ["client delete prohibited", "client transfer prohibited", "client update prohibited"],
        "source": "rdap",
        "server": "rdap.verisign.com",
        "error": null
    },
    "spf": { "found": true, "all": "~all", "dnsLookups": 10, "voidLookups": 0 },
    "dmarc": { "found": true, "policy": "quarantine", "pct": 100, "rua": ["mailto:dmarc@github.com"] },
    "dkim": [
        { "selector": "google", "keyType": "rsa", "keyBits": 2048 },
        { "selector": "selector1", "keyType": "rsa", "keyBits": 1024 }
    ],
    "ssl": {
        "issuer": "Sectigo Limited / Sectigo Public Server Authentication CA DV E36",
        "validTo": "2026-11-29T23:59:59.000Z",
        "daysLeft": 55,
        "protocol": "TLSv1.3",
        "chainOk": true
    },
    "redirect": { "redirectsToHttps": true, "status": 301, "location": "https://github.com/" },
    "findings": [
        {
            "severity": "low",
            "check": "spf",
            "message": "SPF uses 10 of 10 allowed DNS lookups.",
            "recommendation": "Leave headroom. Adding one more provider may break SPF."
        },
        {
            "severity": "low",
            "check": "dmarc",
            "message": "DMARC policy is p=quarantine.",
            "recommendation": "Move to p=reject for full protection."
        }
    ],
    "checkedAt": "2026-10-05T13:05:42.410Z",
    "error": null
}
```

#### Data fields

| Field                      | What it contains                                                                                      |
| -------------------------- | ----------------------------------------------------------------------------------------------------- |
| `grade`, `score`           | A to F grade and 0-100 score. See [How the grade is calculated](#how-the-grade-is-calculated).        |
| `emailProvider`            | Mailbox provider from MX. `No MX` when the domain takes no email, `Self-hosted / other` when unknown. |
| `emailGateway`             | Inbound security gateway in front of the mailbox, or null.                                            |
| `emailProviderSource`      | `mx`, or `spf` when the MX shows only a gateway and SPF reveals the mailbox provider.                 |
| `mxHosts`                  | MX hosts, most preferred first.                                                                       |
| `dns`                      | A, AAAA, MX, NS, TXT and CAA records. `nullMx` marks a "0 ." MX.                                      |
| `whois`                    | Registrar, created, updated, expires, daysToExpiry, domainAgeDays, status, source, server, error.     |
| `spf`                      | Record, mechanisms, `all` qualifier, recursive DNS lookup count, void lookups, includes.              |
| `dmarc`                    | Record, policy, subdomain policy, pct, rua and ruf. Falls back to the parent domain.                  |
| `dkim`                     | Keys found per selector with key type, RSA key size, test mode and revoked flag.                      |
| `dnssec`, `mtaSts`, `bimi` | DNSSEC status, MTA-STS policy and TLS-RPT, BIMI logo and certificate URLs.                            |
| `ssl`                      | Issuer, validFrom, validTo, daysLeft, SANs, TLS version, cipher, chain and hostname check.            |
| `headers`, `redirect`      | HSTS, CSP, X-Frame-Options and other headers. Whether `http://` redirects to `https://`.              |
| `findings`                 | `severity`, `check`, `message` and `recommendation`, worst first.                                     |
| `error`, `errorCode`       | Set when the domain could not be checked, for example it has no DNS records. Such rows are free.      |

### Pricing

Pay per event. You pay only for domains that return a result. Platform usage is included.

| Apify plan     | Price per domain | Per 1,000 domains |
| -------------- | ---------------- | ----------------- |
| Free           | $0.0015          | $1.50             |
| Bronze         | $0.00135         | $1.35             |
| Silver         | $0.0012          | $1.20             |
| Gold and above | $0.00105         | $1.05             |
| Start fee      | $0.001 per run   |                   |

WHOIS, DNS, email provider and every check are included in the per-domain price. Invalid names and domains without DNS records are not charged. The prefilled 4-domain example costs under one cent. Set **Maximum cost per run** in the run options to cap spending. The run stops cleanly at the limit and the status message says how many domains were skipped.

### How the grade is calculated

Every domain starts at **100 points**. Each finding subtracts points by severity:

| Severity | Points | Examples                                                                                   |
| -------- | ------ | ------------------------------------------------------------------------------------------ |
| critical | -30    | SPF `+all`, expired certificate, expired domain registration                               |
| high     | -15    | no SPF, no DMARC, SPF over 10 lookups, certificate for the wrong name, domain expires soon |
| medium   | -7     | DMARC `p=none` or partial quarantine, no DKIM key found, no HSTS, no HTTPS redirect        |
| low      | -2     | no CAA, no DNSSEC, no MTA-STS, missing CSP or nosniff, DMARC `p=quarantine`, 1024-bit DKIM |
| info     | 0      | no BIMI, no Referrer-Policy, SPF `~all` while DMARC enforces                               |

Low findings are hygiene items, so they **count at most 6 points per check**. High and critical findings are never capped.

| Score | Grade | Meaning                                                                 |
| ----- | ----- | ----------------------------------------------------------------------- |
| 90+   | A     | Strong. Only hygiene items left.                                        |
| 80-89 | B     | Good. One real gap or several hygiene items.                            |
| 65-79 | C     | Needs work. Usually a missing SPF, DMARC or HTTPS protection.           |
| 50-64 | D     | Weak. Several real gaps. Any critical finding also caps the grade at D. |
| < 50  | F     | Spoofable or broken. Fix the high and critical findings first.          |

Context rules keep the grade fair:

- A domain **without MX, or with a null MX** (`0 .`), is treated as a non-mail domain. Missing DKIM, MTA-STS and BIMI are not penalised. SPF `-all` and DMARC `p=reject` are still expected, because spoofing does not need an MX.
- SPF `~all` is only info when DMARC is at `quarantine` or `reject` with `pct=100`.
- Certificate expiry warnings scale with the certificate lifetime. A 90-day certificate is flagged below 14 days (high) and 30 days (medium).
- If TLS works but the homepage times out, the site most likely blocks data-center traffic. That is info, not a penalty.

### FAQ

#### How do I look up DNS records and WHOIS for a list of domains?

Paste the list into **Domains** and press **Start**. Open the **DNS & WHOIS** view for A, AAAA, MX, NS, TXT and CAA records next to registrar, creation date and expiry date. Export it as CSV or Excel.

#### How do I find which email provider a company uses?

Every row has `emailProvider`, read from the MX records: Google Workspace, Microsoft 365, Zoho Mail, Proton Mail, Fastmail, iCloud, Yahoo, Amazon WorkMail, GoDaddy, IONOS, OVHcloud, Namecheap, Rackspace, Yandex, Mail.ru and more. When a gateway such as Mimecast or Proofpoint hides the mailbox, `emailGateway` names it and the Actor tries the SPF record for the provider behind it.

#### How do I check SPF, DKIM and DMARC for many domains at once?

Run the Actor on your list and open the **Email security** view. Missing or weak records show up as findings with a fix, for example "No DMARC record" with the record to publish.

#### How do I monitor SSL certificate and domain expiry?

Schedule a daily or weekly run in Apify and add a Slack, email or webhook integration. Filter on `ssl.daysLeft < 14` or `whois.daysToExpiry < 30`.

#### Which TLDs have WHOIS data?

All TLDs with a registry RDAP service (.com, .net, .org, .uk, .fr, .nl, .app, .dev, .info, .au, .ca, .pl, .no, .fi, .br and many more), plus port-43 WHOIS for TLDs without RDAP (.io, .co, .me, .it, .lt, .ee, .se, .dk, .at, .ru, .us and others). Some registries publish little or nothing: .de shows only the status, .ch and .es show no data. The `whois.error` field then says why.

#### Are there WHOIS limits for big lists?

Registries rate limit WHOIS. The Actor throttles each registry server, caches results per run (subdomains share one lookup) and stops WHOIS after `maxWhoisLookups` (default 1,000). A list of thousands of domains on the same TLD therefore takes longer. For very large lists, raise the cap step by step or set `includeWhois` to false. DNS and security checks are not limited.

#### Why does DKIM say "not found" when I sign my mail?

DKIM keys can only be found if you know the selector. Open an email you sent, find `s=` in its `DKIM-Signature` header and add it to `dkimSelectors`.

#### What happens if my input is invalid?

The run still ends **SUCCEEDED** and you pay only the start fee. The dataset gets one free help row, and the status message says the same:

```json
{
    "error": true,
    "errorCode": "EMPTY_INPUT",
    "message": "No domains in input.",
    "howToFix": "Add at least one domain to \"domains\", e.g. [\"apify.com\"]. URLs and email addresses work too."
}
```

`INVALID_INPUT` means none of the entries is a public domain name. In a list that mixes good and bad entries, the good ones are checked and each bad one gets a free row with `errorCode` `INVALID_DOMAIN` or `NO_DNS_RECORDS`.

#### Is this legal?

Yes. The Actor reads public DNS and registry data, does one normal TLS handshake and fetches the homepage headers, like any mail server or browser. It does not scan ports, guess passwords or send email.

#### Known limits

No SMTP connection to MX servers, no port scan and no blacklist lookup. Registrant names are not collected: most registries redact them. The registrable-domain logic is a heuristic, so rare public suffixes may resolve to the wrong parent.

Found a bug or need another field? Open an issue on the **Issues** tab.

### Related Actors

More tools from the same developer, built with the same pricing and output style.

| Actor                                                                                                               | What it does                                                                   |
| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| [Website Content Crawler to Markdown for RAG & LLMs](https://apify.com/rod_analytics/website-to-markdown)           | Crawl whole websites into clean Markdown, RAG chunks and llms.txt              |
| [PDF to Markdown & Text Extractor with OCR](https://apify.com/rod_analytics/doc-to-markdown)                        | PDF, Word, Excel and PowerPoint files to Markdown and text, with OCR           |
| [SEO Audit Tool: Site Crawler, Broken Links & Score](https://apify.com/rod_analytics/seo-site-audit)                | Crawl a site and find broken links and on-page SEO issues                      |
| [PageSpeed Insights & Lighthouse Bulk Checker](https://apify.com/rod_analytics/lighthouse-audit)                    | Lighthouse scores and Core Web Vitals for many URLs at once                    |
| [Tech Stack Detector: Wappalyzer & BuiltWith Alternative](https://apify.com/rod_analytics/tech-stack-detector)      | Detect the CMS, frameworks, analytics and hosting of any website               |
| [Speech to Text: Audio, Video & Podcast Transcriber](https://apify.com/rod_analytics/media-transcriber)             | Whisper transcripts, SRT and VTT subtitles from audio, video and podcast feeds |
| [Email Validator & Verifier: Bulk MX & Disposable Check](https://apify.com/rod_analytics/bulk-email-verifier)       | Check email lists for syntax, MX records and disposable domains                |
| [Website Contact Scraper: Emails, Phones & Social Links](https://apify.com/rod_analytics/company-contact-extractor) | Find emails, phone numbers and social profiles on company websites             |
| [VIES VAT Checker & EU Company Registry Lookup](https://apify.com/rod_analytics/eu-company-lookup)                  | Validate EU VAT numbers and look up company registry data                      |
| [Companies House Scraper: UK Company Search & Officers](https://apify.com/rod_analytics/uk-companies-house)         | Search UK companies and get profiles and officers                              |
| [EU TED Tenders Scraper & Alerts: Public Procurement](https://apify.com/rod_analytics/ted-tenders)                  | EU public tenders and contract awards with daily alerts                        |
| [Greenhouse, Lever & Ashby Job Scraper: 7 ATS in One Feed](https://apify.com/rod_analytics/multi-ats-jobs-api)      | Job postings from 7 applicant tracking systems in one schema                   |
| [GitHub Scraper: Repo Stats, Stars, Trending & Search](https://apify.com/rod_analytics/github-repo-stats)           | Repository stats, stars, trending repos and search results                     |

# Changelog

This Actor's version history is a separate document: https://apify.com/rod_analytics/domain-security-audit/changelog.md

# Actor input Schema

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

Domains to look up, one per line (commas and spaces also separate entries). Bare domains (example.com), subdomains (mail.example.com), URLs (https://www.example.com/page), email addresses (jane@example.com) and IDN names (münchen.de) are accepted. A leading www. is removed so email checks run on the mail domain. Invalid entries are listed in the results with an error and are not charged.

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

DKIM keys can only be found if you know the selector. These common selectors are always checked: google, selector1, selector2, default, k1, s1, mail, dkim. Add your own here, for example the one in the DKIM-Signature header (s=) of an email you received.

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

Leave everything selected for a full audit. Switch off checks you do not need to make runs faster. Only selected checks affect the grade.

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

Adds registrar, creation date, expiry date, days to expiry and domain age for the registrable domain. Uses the registry's RDAP API, or port-43 WHOIS for TLDs without RDAP (.io, .co, .de, .it, ...). Some registries (.de, .ch, .es) publish little or no data, then the whois field explains why. Lookups are throttled per registry and cached per run, so big lists of one TLD take longer. Free, no extra charge.

## `maxWhoisLookups` (type: `integer`):

Safety cap for WHOIS lookups in one run. Subdomains of the same domain share one lookup. Domains past the cap are still audited, only without WHOIS data. Registries rate limit WHOIS, so keep this modest for very large lists.

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

How many domains to audit in parallel.

## `timeoutSecs` (type: `integer`):

Maximum wait for each TLS or HTTP connection. A whole domain audit is capped at 45 seconds.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "github.com",
    "google.com",
    "bbc.co.uk"
  ],
  "dkimSelectors": [],
  "checks": [
    "dns",
    "spf",
    "dmarc",
    "dkim",
    "dnssec",
    "mtaSts",
    "bimi",
    "ssl",
    "headers",
    "redirect"
  ],
  "includeWhois": true,
  "maxWhoisLookups": 1000,
  "maxConcurrency": 10,
  "timeoutSecs": 10
}
```

# Actor output Schema

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

No description

## `dnsWhois` (type: `string`):

No description

## `findings` (type: `string`):

No description

## `results` (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",
        "google.com",
        "bbc.co.uk"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("rod_analytics/domain-security-audit").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",
        "google.com",
        "bbc.co.uk",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("rod_analytics/domain-security-audit").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",
    "google.com",
    "bbc.co.uk"
  ]
}' |
apify call rod_analytics/domain-security-audit --silent --output-dataset

```

## MCP server setup

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

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/YKiBPmiXmFFlOASLs/builds/vD1laDtILPp68Czef/openapi.json
