# Domain WHOIS, DNS, Email Security & TLS Checker (`owlabee/domain-intelligence`) Actor

Enrich any list of domains with RDAP WHOIS (registrar, expiry), DNS records, SPF/DMARC/DKIM/MTA-STS/BIMI email security score, TLS certificate expiry, HTTP security headers and tech stack hints. One JSON record per domain, pay per domain.

- **URL**: https://apify.com/owlabee/domain-intelligence.md
- **Developed by:** [Owlabee](https://apify.com/owlabee) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 domain enricheds

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, Email Security & TLS Checker

### What it does

Give this Actor a list of domains and it returns **one enriched JSON record per domain** with everything a sales, security or IT team usually has to collect from four different tools:

- **WHOIS via RDAP** — registrar, registration / expiration / last-changed dates, days until the domain expires, EPP status codes, nameservers, DNSSEC, and registrant organisation / country when the registry publishes them (redacted fields stay `null`; nothing is guessed).
- **DNS records** — A, AAAA, MX (sorted by priority), NS, TXT, CAA and SOA over DNS-over-HTTPS, plus a mail-provider guess (Google, Microsoft, Zoho, Proton, Fastmail, Mimecast, Proofpoint, other).
- **Email security** — SPF record and syntax check (lookup count, `-all` / `~all` / `?all`), DMARC policy, subdomain policy, `pct`, aggregate/forensic reporting addresses, DKIM selectors found (18 common selectors probed, plus your own), MTA-STS record and policy mode, BIMI record, and an **email security score 0–100**.
- **TLS certificate** — subject CN, issuer, valid-from / valid-to, days until expiry, SAN count, protocol version and whether the chain actually verifies.
- **HTTP** — final URL, status code, full redirect chain, `server` / `x-powered-by`, security headers (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy), page title, meta generator and **tech stack hints** (Shopify, WordPress, Wix, Squarespace, Webflow, HubSpot, Cloudflare, Next.js, Nuxt, Drupal, Ghost, Framer, Vercel, Netlify, Google Tag Manager, Google Analytics, Intercom, Stripe).

Every section is independent: if a registry is slow or a site is down, the other sections are still filled in and the problem is recorded in `errors`. It only uses public RDAP, public DNS resolvers and the domain's own public endpoints — no API keys, no proxies, no browser — which is why it costs **$0.0015 per domain**. Results are available as JSON, CSV, Excel, via API, webhooks or the Apify MCP server, like any Apify dataset.

Typical uses: lead enrichment and segmentation by tech stack and mail provider, email deliverability audits (SPF/DMARC/DKIM gaps), certificate and domain expiry monitoring, M\&A / vendor due diligence, and competitor technology research.

### How to use

1. Paste your **Domains** (one per line). URLs such as `https://www.example.com/pricing` and email addresses such as `jane@example.com` are fine — the registrable domain is extracted and duplicates are merged.
2. Optionally untick **Sections** you do not need, add **Extra DKIM selectors** your ESP uses, and set **Max concurrency**.
3. Click **Start**. Each domain is written to the dataset as soon as it is finished; download the table or connect it to your CRM, spreadsheet or agent via the API.

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `domains` | string\[] | yes | Domains, URLs or email addresses to check. Deduplicated by registrable domain. |
| `sections` | string\[] | no | Any of `whois`, `dns`, `tls`, `http`. Default: all four. |
| `dkimSelectors` | string\[] | no | Extra DKIM selectors to probe in addition to the built-in 18. |
| `maxConcurrency` | integer | no | Domains checked in parallel (1–20, default 5). |
| `webhookUrl` | string | no | POST target for the run summary (counts, charges, finish time). |

### Output example

```json
{
  "domain": "github.com",
  "input": "https://github.com/apify",
  "fetchedAt": "2026-10-16T12:00:00.000Z",
  "sections": ["whois", "dns", "tls", "http"],
  "whois": {
    "status": "ok",
    "source": "rdap.markmonitor.com",
    "registrar": "MarkMonitor Inc.",
    "registrarIanaId": "292",
    "registrationDate": "2007-10-09T18:20:50.000Z",
    "expirationDate": "2026-10-09T18:20:50.000Z",
    "lastChangedDate": "2024-09-07T09:16:33.000Z",
    "daysUntilExpiry": 358,
    "registered": true,
    "domainStatus": ["client delete prohibited", "client transfer prohibited", "client update prohibited"],
    "nameservers": ["dns1.p08.nsone.net", "dns2.p08.nsone.net", "ns-1283.awsdns-32.org", "ns-1707.awsdns-21.co.uk"],
    "dnssecSigned": false,
    "registrantOrg": "GitHub, Inc.",
    "registrantCountry": "US"
  },
  "dns": {
    "status": "ok",
    "resolver": "cloudflare",
    "nxdomain": false,
    "a": ["140.82.121.4"],
    "aaaa": [],
    "mx": [{ "priority": 1, "exchange": "aspmx.l.google.com" }, { "priority": 5, "exchange": "alt1.aspmx.l.google.com" }],
    "ns": ["dns1.p08.nsone.net", "dns2.p08.nsone.net"],
    "txt": ["v=spf1 ip4:192.30.252.0/22 include:_netblocks.google.com include:_netblocks2.google.com include:_netblocks3.google.com include:spf.protection.outlook.com include:mail.zendesk.com include:_spf.salesforce.com include:servers.mcsv.net ip4:166.78.69.169 ip4:166.78.69.170 ip4:166.78.71.131 ip4:167.89.101.2 ip4:167.89.101.192/28 ip4:192.254.112.60 ip4:192.254.112.98/31 ip4:192.254.113.10 ip4:192.254.113.101 ip4:192.254.114.176 ip4:62.253.227.114 ~all"],
    "caa": [{ "flags": 0, "tag": "issue", "value": "digicert.com" }],
    "soa": { "mname": "dns1.p08.nsone.net", "rname": "hostmaster.nsone.net", "serial": 1718097213, "refresh": 43200, "retry": 7200, "expire": 1209600, "minimum": 3600 },
    "hasMx": true,
    "mxProviderGuess": "google",
    "spfRecord": "v=spf1 ip4:192.30.252.0/22 include:_netblocks.google.com ... ~all",
    "spfValid": true,
    "spfAllQualifier": "~all",
    "spfLookupCount": 7,
    "spfIssues": [],
    "dmarcRecord": "v=DMARC1; p=reject; pct=100; rua=mailto:dmarc@github.com",
    "dmarcPolicy": "reject",
    "dmarcSubdomainPolicy": null,
    "dmarcPct": 100,
    "hasRua": true,
    "hasRuf": false,
    "dmarcIssues": [],
    "dkimSelectorsProbed": 18,
    "dkimSelectorsFound": ["google", "pm"],
    "mtaStsPresent": true,
    "mtaStsRecord": "v=STSv1; id=20230901",
    "mtaStsPolicyMode": "enforce",
    "bimiRecord": null
  },
  "tls": {
    "status": "ok",
    "protocol": "TLSv1.3",
    "authorized": true,
    "authorizationError": null,
    "subjectCN": "github.com",
    "issuerOrg": "Sectigo Limited",
    "issuerCN": "Sectigo ECC Domain Validation Secure Server CA",
    "validFrom": "2026-02-05T00:00:00.000Z",
    "validTo": "2027-02-05T23:59:59.000Z",
    "daysUntilExpiry": 112,
    "sanCount": 2,
    "fingerprint256": "A1:B2:..."
  },
  "http": {
    "status": "ok",
    "finalUrl": "https://github.com/",
    "statusCode": 200,
    "redirectChain": [],
    "server": "github.com",
    "poweredBy": null,
    "contentType": "text/html; charset=utf-8",
    "securityHeaders": {
      "hsts": "max-age=31536000; includeSubdomains; preload",
      "csp": "default-src 'none'; base-uri 'self'; ...",
      "xFrameOptions": "deny",
      "xContentTypeOptions": "nosniff",
      "referrerPolicy": "origin-when-cross-origin, strict-origin-when-cross-origin"
    },
    "pageTitle": "GitHub · Build and ship software on a single, collaborative platform",
    "metaGenerator": null,
    "techHints": ["Stripe"]
  },
  "registrar": "MarkMonitor Inc.",
  "expirationDate": "2026-10-09T18:20:50.000Z",
  "mxProviderGuess": "google",
  "dmarcPolicy": "reject",
  "tlsDaysUntilExpiry": 112,
  "httpStatusCode": 200,
  "techHints": ["Stripe"],
  "emailSecurityScore": 95,
  "summary": "github.com: registered via MarkMonitor Inc., expires in 358 days; MX google, SPF ok (~all), DMARC reject, DKIM google/pm, MTA-STS; email security 95/100; TLS valid, cert 112 days left, by Sectigo Limited; HTTP 200, server github.com, tech: Stripe",
  "errors": {}
}
```

The values above are illustrative; live records reflect what the registry, resolvers and the site return at run time. The top-level `registrar`, `expirationDate`, `mxProviderGuess`, `dmarcPolicy`, `tlsDaysUntilExpiry`, `httpStatusCode`, `techHints`, `emailSecurityScore` and `summary` columns are flattened copies so the Console table and CSV export stay readable.

Each section has a `status`: `ok`, `error` (reached the source, got a bad answer), `unavailable` (could not reach the source at all — for example a domain with no web server on port 443) or `skipped` (not requested). When a section fails, `errors` holds the reason, e.g. `{ "tls": "ECONNREFUSED: connect ECONNREFUSED 93.184.216.34:443" }`, and the remaining sections are still returned.

Inputs that are not a domain at all produce `{ "input": "not a domain", "error": "INVALID_DOMAIN", "errorMessage": "..." }` and are charged at the same rate, because the work to classify them was done.

#### Email security score

| Check | Points |
|---|---|
| SPF record present and syntactically valid (≤ 10 lookups, no unknown terms, single record) | 25 |
| DMARC policy `reject` / `quarantine` / `none` | 35 / 20 / 5 |
| At least one DKIM selector found | 25 |
| MTA-STS record present | 10 |
| BIMI record present | 5 |

### Pricing

Pay per event. You only pay for records the Actor produces; set a **maximum cost per run** in the run options to cap spend.

| Event | Price | Notes |
|---|---|---|
| Domain enriched | $0.0015 | Per domain record written to the dataset, including error records |
| Actor start | $0.00005 | Apify platform event |

**Example:** 1,000 domains ≈ **$1.50**. Running fewer sections does not change the price per domain, it only makes the run faster.

### Limits and fair use

- Each domain triggers roughly 30 small requests: one RDAP lookup, about 28 DNS-over-HTTPS queries (7 record types, DMARC, MTA-STS, BIMI, 18 DKIM selectors), one TLS handshake and one HTTP GET (≤ 5 redirects, body capped at 512 KB). Public DNS resolvers are built for this volume; registries and target sites receive at most one request per domain.
- Timeouts: 15 s for RDAP and HTTP, 8 s per DNS query and for the TLS handshake. A slow source marks its section `error`/`unavailable`; it never blocks the other sections.
- DKIM can only be *discovered*, not enumerated: a domain whose selector is not in the probed list shows `dkimSelectorsFound: []` even though DKIM is configured. Add your selectors in `dkimSelectors`.
- The SPF lookup count covers the top-level record only (includes are not recursed), so it is a lower bound.
- Registrable-domain extraction uses a curated list of common multi-label suffixes (`co.uk`, `com.au`, `github.io`, …) rather than the full Public Suffix List; exotic suffixes may be reduced one label too far.
- Tech hints are header/HTML fingerprints of the landing page only and are meant as a signal, not an audit.
- Subdomains are reduced to their registrable domain (`mail.example.com` → `example.com`). To inspect a specific host's certificate, run the host through a dedicated TLS scanner.

### Related Actors

Other pay-per-event Actors from the same account, all limited-permission and agent-friendly:

- [Website Change Monitor — Sitemap & Page Content Diff](https://apify.com/owlabee/website-change-monitor) — watch the pages behind these domains for new, removed and changed content.
- [Shopify Store Products Scraper & Price Change Monitor](https://apify.com/owlabee/shopify-catalog-monitor) — when the tech hints say Shopify, pull the store's full catalog or monitor its prices.
- [Company Job Boards Scraper & Hiring Signals (ATS)](https://apify.com/owlabee/ats-hiring-signals) — add open roles and hiring velocity from the company's public job board.

### FAQ

**Does this need proxies or an API key?** No. RDAP, DNS-over-HTTPS (Cloudflare, with Google as fallback) and the domains' own public endpoints are all free and open; the Actor runs on Apify's standard datacenter infrastructure.

**How is cost controlled?** Every domain is charged the moment its record is saved, and the Actor stops cleanly when the run's maximum total charge is reached. Deduplication means you never pay twice for `www.example.com` and `example.com`.

**Why is a WHOIS field empty?** Most registries redact personal data under GDPR/ICANN rules, and some ccTLDs do not offer RDAP at all. The Actor reports exactly what the registry returns and never fills gaps with guesses; `whois.source` tells you which server answered.

**Why does `dkimSelectorsFound` say nothing when I know DKIM is set up?** DKIM selectors are not discoverable from the outside; the Actor probes 18 common ones. Add your provider's selector in `dkimSelectors` (for example `fm1`, `fm2`, `fm3` for Fastmail or `krs` for Klaviyo).

**What is `tls.status: "unavailable"`?** Nothing answered on port 443 (no web server, firewall, or DNS does not resolve). It is a finding about the domain, not an Actor error; the other sections are still filled.

**Can I run it on a schedule or call it from an AI agent?** Yes — use Apify Schedules, the API, or the Apify MCP server; this Actor is pay-per-event and uses limited permissions, so agents can discover and call it. Example prompts: "Check SPF, DMARC and DKIM for these 50 customer domains and flag any without a reject policy", "Which of these competitor domains run on Shopify and use Google Workspace for email?", "List domains in this sheet whose TLS certificate or registration expires in the next 30 days".

### Legal

The Actor reads only public registration data (RDAP), public DNS, public TLS certificates and the public landing page of each domain, at a rate of one request per domain per source. Use the data in compliance with the registries' terms and applicable law. This Actor is not a consumer reporting agency and its output must not be used for employment, credit, insurance, or tenant screening decisions.

# Actor input Schema

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

Domains to check, one per line. Full URLs (https://www.example.com/page) and email addresses (jane@example.com) are accepted; the registrable domain is extracted and duplicates are merged. The prefilled values are the cheap, always-available examples used by Apify's daily automated test.

## `sections` (type: `array`):

Which checks to run for every domain. WHOIS = RDAP registration data; DNS = records plus SPF/DMARC/DKIM/MTA-STS/BIMI and the email security score; TLS = certificate on port 443; HTTP = redirects, security headers and tech stack hints. Fewer sections = faster runs; the price per domain is the same.

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

Additional DKIM selectors to probe on top of the built-in list (google, default, selector1, selector2, k1, k2, k3, mail, s1, s2, dkim, mandrill, mailgun, sendgrid, amazonses, zoho, pm, protonmail). Each selector costs one DNS query per domain.

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

How many domains are checked in parallel. 5 is polite and fast enough for a few thousand domains; raise it for large lists.

## `webhookUrl` (type: `string`):

If set, the Actor POSTs a JSON summary of the run (counts, charges, finish time) to this URL when it finishes. Failures are logged, never fatal.

## Actor input object example

```json
{
  "domains": [
    "example.com",
    "apify.com",
    "github.com"
  ],
  "sections": [
    "whois",
    "dns",
    "tls",
    "http"
  ],
  "dkimSelectors": [],
  "maxConcurrency": 5
}
```

# Actor output Schema

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

One item per domain in the default dataset (domain, registrar, expirationDate, mxProviderGuess, dmarcPolicy, emailSecurityScore, tlsDaysUntilExpiry, httpStatusCode, techHints, summary; error items carry error and errorMessage). Fields are described in dataset_schema.json.

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

SUMMARY record in the run's default key-value store: domains processed, records produced and charges, written when the run finishes.

# 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": [
        "example.com",
        "apify.com",
        "github.com"
    ],
    "sections": [
        "whois",
        "dns",
        "tls",
        "http"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("owlabee/domain-intelligence").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": [
        "example.com",
        "apify.com",
        "github.com",
    ],
    "sections": [
        "whois",
        "dns",
        "tls",
        "http",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("owlabee/domain-intelligence").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": [
    "example.com",
    "apify.com",
    "github.com"
  ],
  "sections": [
    "whois",
    "dns",
    "tls",
    "http"
  ]
}' |
apify call owlabee/domain-intelligence --silent --output-dataset

```

## MCP server setup

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

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/UhyhEXvAqRIyl6SAt/builds/FS4m8pqlkx6HB4WeE/openapi.json
