# Domain Intel: WHOIS (RDAP), DNS, SSL & Domain Age (`blond_terrarium/domain-intel-whois-dns-ssl`) Actor

Bulk domain lookup in one row: WHOIS via RDAP (registrar, creation/expiry dates, domain age, status, nameservers, DNSSEC, abuse contact), DNS (A, AAAA, MX, NS, TXT, CAA, SOA), SPF/DMARC, email provider and SSL certificate expiry. Pay only for domains with data.

- **URL**: https://apify.com/blond_terrarium/domain-intel-whois-dns-ssl.md
- **Developed by:** [Mogul Tools](https://apify.com/blond_terrarium) (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 $2.50 / 1,000 domain analyzeds

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

## Domain Intel: WHOIS (RDAP) + DNS + SSL + Domain Age, in bulk

Paste a list of domains, URLs or email addresses and get **one clean row per domain**. Each row has the registrar, creation and expiry dates, domain age, nameservers, MX and email provider, SPF and DMARC, CAA, and the live SSL certificate (issuer, expiry, validity). Lookups use official **RDAP** (the modern, structured WHOIS) through the IANA bootstrap, DNS over Cloudflare DoH with a system-resolver fallback, and a direct TLS handshake on port 443. No browser and no proxies, so runs are fast.

**You pay only for domains that return real data.** Invalid inputs, unregistered domains and domains with no data are written free.

### Use cases

- **Lead enrichment:** add domain age, email provider (Google Workspace, Microsoft 365, Proofpoint…), registrar and company country to a lead list. Enter emails (`ceo@company.com`) directly.
- **Portfolio and expiry monitoring:** track expiry dates, registrar lock statuses and nameservers across hundreds of domains. `expiringSoon` flags anything expiring within 30 days (configurable).
- **Email deliverability audit:** check `hasMx`, `hasSpf`, the SPF `all` qualifier, `hasDmarc` and `dmarcPolicy` (none/quarantine/reject) for every domain you send to or own.
- **SSL expiry checks:** get the certificate issuer, `sslValidTo`, `sslDaysUntilExpiry` and `sslValid`. Expired, self-signed, wrong-host and untrusted certificates are reported with the reason.
- **Fraud and new-domain screening:** `domainAgeDays` flags domains registered in the last few weeks.

### Input

| Field | Default | Notes |
|---|---|---|
| `domains` | – | Domains, URLs or emails. Each is normalized to its registrable domain (`https://www.bbc.co.uk/news` → `bbc.co.uk`, `ceo@stripe.com` → `stripe.com`). Duplicates are skipped. |
| `includeWhois` / `includeDns` / `includeSsl` | true | Turn sections off for speed. |
| `followRegistrarRdap` | true | Also query the registrar's RDAP server to fill in gaps (abuse contact, registrant org/country). |
| `expiringSoonDays` | 30 | Threshold for `expiringSoon` (domain or certificate). |
| `maxConcurrency` | 10 | Up to 50. RDAP is paced per registry/registrar either way. |
| `maxChargedDomains` | 0 (no limit) | Stop after this many charged domains. |
| `includeFailedDomains` | true | Write free rows for invalid/unregistered/no-data inputs. |

```json
{ "domains": ["google.com", "https://www.bbc.co.uk/news", "ceo@stripe.com"] }
```

### Output (one row per domain, abridged)

```json
{
  "domain": "google.com", "status": "OK", "isRegistered": true,
  "registrar": "MarkMonitor Inc.", "registrarIanaId": "292",
  "createdAt": "1997-09-15T04:00:00Z", "expiresAt": "2028-09-14T04:00:00Z",
  "domainAgeDays": 10632, "domainAgeYears": 29.1, "daysUntilExpiry": 691,
  "statusCodes": ["client delete prohibited", "server transfer prohibited", "..."],
  "nameservers": ["ns1.google.com", "ns2.google.com", "ns3.google.com", "ns4.google.com"],
  "registrantRedacted": true, "registrantCountry": "US", "abuseEmail": "abusecomplaints@markmonitor.com",
  "hasMx": true, "emailProvider": "Google Workspace", "hasSpf": true, "spfAllQualifier": "~all",
  "hasDmarc": true, "dmarcPolicy": "reject", "caaIssuers": ["pki.goog"],
  "sslValid": true, "sslIssuer": "Google Trust Services", "sslValidTo": "2026-12-21T...",
  "sslDaysUntilExpiry": 58, "expiringSoon": false, "expiringSoonReasons": [],
  "whois": { "...": "full parsed RDAP" }, "dns": { "a": [], "mx": [], "txt": [], "...": "" }, "ssl": { "sans": [], "protocol": "TLSv1.3", "...": "" },
  "charged": true
}
```

`status` is `OK` (charged), `NOT_REGISTERED`, `NO_DATA` or `INVALID_INPUT` (all free). A run summary is saved to the key-value store as `RUN_SUMMARY`.

### Pricing (pay per event)

One event, **`domain-analyzed`**. It is charged once per unique domain when WHOIS, DNS or SSL returned real data for a registered domain.

| Tier | Per domain | Per 1,000 domains |
|---|---|---|
| FREE | $0.004 | $4.00 |
| BRONZE | $0.0035 | $3.50 |
| SILVER | $0.003 | $3.00 |
| GOLD | $0.0025 | $2.50 |

**Free:** invalid inputs (not a domain, IPs, localhost, unknown TLDs), duplicates, unregistered domains and domains where every lookup failed. Apify's standard actor-start event ($0.00005) applies. Set a max total charge or `maxChargedDomains` to cap spend. The run stops cleanly at your limit.

### Limitations

- **GDPR / privacy redaction:** most gTLD registrars redact registrant names, emails and addresses. We output only what is public (organization, country, state when given) and flag `registrantRedacted`. Personal names are never output.
- **ccTLDs without RDAP:** some registries (for example `.ru`, `.jp`, `.eu`, `.it`, `.cn`) have no public RDAP. These domains still get DNS and SSL data and are charged only if that data exists. `whois.errorCode` is `NO_RDAP_SERVER`. Some registries publish only minimal RDAP, e.g. DENIC (`.de`) gives status, nameservers and last-changed date but no registrar or dates.
- **Registrar rate limits:** registry lookups are paced and retried with backoff on 429. The registrar follow-up is best effort. Some registrars (e.g. GoDaddy) rate-limit cloud IPs, and then the registry data alone is returned.
- SSL is checked on port 443 only, on the host you entered (or the bare domain, then `www`). Port 80/HTTP is not checked.
- `emailProvider` is based on MX hostnames. `Self-hosted` means the MX points at the domain's own infrastructure and `Other` means an unrecognized provider.

# Actor input Schema

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

One per line. Bare domains (example.com), URLs (https://www.example.co.uk/page) and email addresses (jane@example.com) are all reduced to the registrable domain (example.co.uk). Duplicates are checked once.

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

Registrar, IANA ID, creation/expiry/updated dates, domain age, status codes, nameservers, DNSSEC, registrant organization/country (when not redacted) and abuse contact, from the official RDAP servers.

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

A, AAAA, MX, NS, TXT, CAA, SOA and \_dmarc records, plus email provider (Google Workspace, Microsoft 365...), SPF and DMARC policy flags.

## `includeSsl` (type: `boolean`):

TLS connection on port 443 (domain, then www.): issuer, subject, SANs, valid from/to, days until expiry, protocol and whether the certificate is valid.

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

For .com/.net and other thin registries, also query the registrar's RDAP server for registrant organization/country and abuse contact. One extra request per domain.

## `expiringSoonDays` (type: `integer`):

Sets expiringSoon = true when the domain registration or the SSL certificate expires within this many days (or already has).

## `maxChargedDomains` (type: `integer`):

Stop after this many analyzed (charged) domains. 0 = no limit (the run's Max total charge still applies).

## `includeFailedDomains` (type: `boolean`):

Adds a free row with the reason (INVALID_INPUT, NOT_REGISTERED, NO_DATA) so every input is accounted for.

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

RDAP servers are paced per registry and back off on 429 automatically, so higher values mostly help with mixed TLDs.

## `dnsResolver` (type: `string`):

Auto uses the platform resolver and switches to Cloudflare DoH if it doesn't answer MX/TXT/NS.

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

Per RDAP request; the TLS handshake uses at most 10 s.

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

Optional, usually not needed. Apify datacenter proxy or your own proxy URLs for RDAP requests. Residential proxy isn't supported.

## Actor input object example

```json
{
  "domains": [
    "google.com",
    "https://www.bbc.co.uk/news",
    "jane@shopify.com"
  ],
  "includeWhois": true,
  "includeDns": true,
  "includeSsl": true,
  "followRegistrarRdap": true,
  "expiringSoonDays": 30,
  "maxChargedDomains": 0,
  "includeFailedDomains": true,
  "maxConcurrency": 10,
  "dnsResolver": "auto",
  "requestTimeoutSecs": 15,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Registrar, dates, domain age, email provider, SPF/DMARC and SSL expiry per domain.

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

Every row incl. full DNS records, RDAP details and the SSL certificate.

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

Totals: analyzed (charged), not registered, no data and invalid inputs (not charged).

# 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": [
        "google.com",
        "https://www.bbc.co.uk/news",
        "jane@shopify.com"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("blond_terrarium/domain-intel-whois-dns-ssl").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": [
        "google.com",
        "https://www.bbc.co.uk/news",
        "jane@shopify.com",
    ],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("blond_terrarium/domain-intel-whois-dns-ssl").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": [
    "google.com",
    "https://www.bbc.co.uk/news",
    "jane@shopify.com"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call blond_terrarium/domain-intel-whois-dns-ssl --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,blond_terrarium/domain-intel-whois-dns-ssl"
        }
    }
}
```

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/hkELDZavUsOWCHcV0/builds/bXdk89wRreRpqmZ2r/openapi.json
