# Domain WHOIS & DNS Lookup (`sfoln/domain-intelligence`) Actor

Bulk WHOIS and DNS lookup: registrar, creation and expiry dates (RDAP), availability, DNS records, SPF/DMARC and mail provider, SSL certificate, plus the homepage security-header grade and tech hints, one JSON record per domain. $3 per 1,000 domains; unregistered domains are free.

- **URL**: https://apify.com/sfoln/domain-intelligence.md
- **Developed by:** [SFOLN LLC](https://apify.com/sfoln) (community)
- **Categories:** Developer tools, SEO tools, Automation
- **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 reports

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

Look up any list of domains and get **one complete record per domain**: registration data (registrar, creation and expiry dates, status, nameservers, DNSSEC) from **RDAP** — the official successor of WHOIS — plus **availability** (can the domain be registered?), **DNS records** (A, AAAA, MX, NS, TXT), **email security** (SPF, DMARC policy, detected mail provider), the **SSL/TLS certificate** (issuer, expiry, SANs, protocol) and a **homepage check** (HTTP status, redirects, final URL, title, **security-header grade A–F**, favicon and detected technologies). Built for lead qualification, security and compliance audits, domain portfolio monitoring, brand protection and AI agents.

- **Price:** $3 per 1,000 domains (`domain-intelligence`), no subscription. Pay **only per analysed domain** – unregistered domains, host names that do not exist, invalid input and domains where every check failed are not charged.
- **Availability, DNS first:** a registrable domain that answers NXDOMAIN and that its registry's RDAP service confirms it has no record of is reported `available: true`; on a TLD whose registry offers no RDAP service (many country-code TLDs) such a name stays `available: null`, because a registered name on hold answers NXDOMAIN too.
- Partial results instead of failures: each check reports its own status in `sections`, problems are listed in `warnings`.
- URLs and fully-qualified names are accepted (`https://www.example.com/page`, `example.com.` → `example.com`); the registrable domain comes from the Public Suffix List (`news.bbc.co.uk` → `bbc.co.uk`); duplicates are removed.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `domains` | string\[] | – | Domains or URLs, one per line. Objects with a `domain` key work too. |
| `checkTls` | boolean | true | Read the SSL/TLS certificate on port 443. |
| `checkHomepage` | boolean | true | Fetch the homepage (https, falling back to http; 8 s timeout, first 512 KB) for status, title, security-header grade, favicon and technologies. |
| `maxDomains` | integer | 1000 | Safety cap for this run; domains beyond it are ignored and counted as `inputItemsDropped` in the `OUTPUT` record. |
| `concurrency` | integer | 5 | Domains analysed in parallel (1–10). |
| `includeRaw` | boolean | false | Attach the raw RDAP JSON as `raw.rdap` (when the registry answered). |

```json
{
  "domains": ["apify.com", "https://www.wikipedia.org/"],
  "checkTls": true,
  "checkHomepage": true
}
```

### Output

One dataset record per domain (nested objects shortened):

```json
{
  "input": "apify.com",
  "domain": "apify.com",
  "registrableDomain": "apify.com",
  "registered": true,
  "available": false,
  "registrar": "Amazon Registrar, Inc.",
  "createdAt": "2009-06-02T17:14:10Z",
  "expiresAt": "2035-06-02T17:14:10Z",
  "domainAgeDays": 6342,
  "mailProvider": "google",
  "spfRecord": "v=spf1 a mx include:_spf.google.com include:mailgun.org -all",
  "dmarcPolicy": "reject",
  "sslIssuer": "R11",
  "sslValidTo": "2026-12-01T12:00:00.000Z",
  "sslDaysUntilExpiry": 48,
  "httpStatus": 200,
  "finalUrl": "https://apify.com/",
  "title": "Apify: Full-stack web scraping and data extraction platform",
  "techHints": ["Algolia", "Node.js", "Next.js", "React", "Amazon Cloudfront", "HSTS", "Intercom"],
  "securityHeaders": { "grade": "B", "score": 85, "present": ["Strict-Transport-Security", "Content-Security-Policy", "X-Frame-Options", "X-Content-Type-Options", "Permissions-Policy"], "missing": ["Referrer-Policy"] },
  "faviconUrl": "https://apify.com/favicon.ico",
  "sections": { "rdap": "ok", "dns": "ok", "tls": "ok", "http": "ok" },
  "whois": { "registrar": "Amazon Registrar, Inc.", "registrarIanaId": "468", "updatedAt": "2026-05-16T16:53:04Z", "daysUntilExpiry": 3153, "statuses": ["client transfer prohibited"], "nameservers": ["ns-449.awsdns-56.com", "…"], "dnssec": true, "registrantOrganization": null, "registrantCountry": null, "abuseEmail": "trustandsafety@support.aws.com" },
  "dns": { "a": ["143.204.204.5"], "aaaa": ["2600:9000:…"], "mx": [{ "priority": 1, "exchange": "aspmx.l.google.com" }], "ns": ["…"], "txt": ["…"], "spf": { "record": "v=spf1 …", "allQualifier": "-all", "includes": ["_spf.google.com", "mailgun.org"], "multipleRecords": false }, "dmarc": { "record": "v=DMARC1; p=reject; …", "policy": "reject", "subdomainPolicy": "reject", "pct": 100, "rua": ["mailto:…"] }, "hasMx": true, "nxdomain": false },
  "tls": { "issuer": "R11", "issuerOrganization": "Let's Encrypt", "subjectCommonName": "apify.com", "sanCount": 2, "validFrom": "…", "validTo": "…", "daysUntilExpiry": 48, "expired": false, "coversHost": true, "trusted": true, "protocol": "TLSv1.3" },
  "http": { "status": 200, "finalUrl": "https://apify.com/", "redirects": 0, "https": true, "hsts": true, "server": null, "title": "…", "generator": null, "techHints": ["…"] },
  "status": "ok",
  "charged": true,
  "chargedEvent": "domain-intelligence",
  "source": "public-rdap-dns-tls",
  "fetchedAt": "2026-10-14T10:00:00.000Z"
}
```

| Field | Description |
| --- | --- |
| `registered` | `true` if an RDAP record or DNS data exists, `false` if the registry's RDAP service has no record and DNS says NXDOMAIN, `null` if undetermined. |
| `available` | `true` when the registrable domain answers NXDOMAIN in DNS and the registry's RDAP service has no record for it (it can be registered); `false` when it resolves or RDAP has a record; `null` when undetermined (the registry did not answer or offers no RDAP service, or the name is outside the Public Suffix List). |
| `registrar`, `createdAt`, `expiresAt`, `domainAgeDays`, `whois.*` | Registration data from the registry's RDAP service. Registrant details are often redacted for privacy and then `null`. |
| `mailProvider` | Guessed from MX hosts: `google`, `microsoft`, `zoho`, `proton`, `yahoo`, `icloud`, `fastmail`, `yandex`, `mimecast`, `proofpoint`, `amazon-ses`, `other`; `none` for a null MX; `null` without MX records. |
| `spfRecord`, `dmarcPolicy`, `dns.*` | DNS records via DNS-over-HTTPS; DMARC read from `_dmarc.<domain>` (falls back to the registrable domain). |
| `ssl*`, `tls.*` | Certificate presented on port 443 (`trusted` = chain validates, `coversHost` = name matches). |
| `httpStatus`, `finalUrl`, `title`, `http.*` | Homepage response after up to 5 redirects (https first, then http). |
| `techHints` | Technologies detected on the homepage from headers, cookies, meta tags, scripts and HTML (up to 30). For a full profile with DNS-verified SaaS, versions and categories use Tech Stack Detector. |
| `securityHeaders` | Homepage security-header grade: `grade` A–F, `score` 0–100 (HSTS 25, CSP 25, X-Frame-Options 15, X-Content-Type-Options 15, Referrer-Policy 10, Permissions-Policy 10 points), `present[]`, `missing[]`. |
| `faviconUrl` | The icon the homepage declares (`<link rel="icon">`). |
| `sections` | Per-check status: `ok`, `not_found`, `error` or `skipped` (TLS/homepage are skipped when the domain has no A/AAAA records). DNS is `error` when the resolver could not answer for any record type (SERVFAIL, REFUSED: e.g. lame delegations or broken DNSSEC); failed record types are listed in `warnings`. |

**`status`**: `ok` – at least one check returned data (charged; failed checks are in `warnings`; a SERVFAIL/REFUSED DNS answer is a failed check, not data). `not_found` – the domain is not registered (`not_registered`: no RDAP record and NXDOMAIN; `available` tells whether it can be registered), or the host name does not exist under a registered domain (`host_not_found`, e.g. a mistyped subdomain: look up the registrable domain instead), not charged. `error` – no check returned data (this includes a name that answers NXDOMAIN on a TLD without an RDAP service: its registration is unknown), not charged. `skipped_invalid` – not a domain (IP addresses and local names are rejected), not charged. `skipped_budget` / `skipped_free_plan` – not processed because of your cost cap or the free-plan limit. The key-value store record `OUTPUT` holds run totals.

### Pricing

| Event | Price | When charged |
| --- | --- | --- |
| `domain-intelligence` | **$0.003** per domain ($3 / 1,000) | once per domain with `status: "ok"` |

Set **Maximum cost per run** to cap spend; when the cap is reached the remaining domains are returned with `status: "skipped_budget"` and are not charged. Accounts on the Apify free plan can process up to 10 items per run; upgrade to any paid plan for full batches.

### Use with AI agents (MCP)

Use this Actor from Claude, ChatGPT, Cursor, VS Code or any other MCP client through the [Apify MCP server](https://mcp.apify.com). Add this server URL to your client and sign in with your Apify account when asked:

```text
https://mcp.apify.com?tools=sfoln/domain-intelligence
```

To run the MCP server locally instead, start `npx -y @apify/actors-mcp-server --tools sfoln/domain-intelligence` with your `APIFY_TOKEN` in the environment.

Example prompt:

> Who is the registrar of apify.com, when does it expire, which mail provider does it use, and is example-brand-name-zz9.com still available?

Runs started by an agent are billed like any other run (see Pricing). Agents can cap what a run may spend with `maxTotalChargeUsd`.

### API usage

Run the Actor and get its dataset items in one call (the call waits up to 5 minutes for the run to finish):

```bash
curl -X POST "https://api.apify.com/v2/acts/sfoln~domain-intelligence/run-sync-get-dataset-items" \
  -H "Authorization: Bearer <APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["apify.com", "wikipedia.org"]}'
```

For large inputs, start the run asynchronously and download the dataset when it has finished:

```bash
## 1. Start a run: the response holds the run ID (data.id) and its dataset ID (data.defaultDatasetId)
curl -X POST "https://api.apify.com/v2/acts/sfoln~domain-intelligence/runs" \
  -H "Authorization: Bearer <APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["apify.com", "wikipedia.org"]}'
## 2. Repeat until data.status is SUCCEEDED (each call waits up to 60 s)
curl "https://api.apify.com/v2/actor-runs/<runId>?waitForFinish=60" -H "Authorization: Bearer <APIFY_TOKEN>"
## 3. Download the results (format=json, csv or xlsx)
curl "https://api.apify.com/v2/datasets/<defaultDatasetId>/items?format=json" -H "Authorization: Bearer <APIFY_TOKEN>"
```

Python ([apify-client](https://pypi.org/project/apify-client/)):

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("sfoln/domain-intelligence").call(run_input={"domains": ["apify.com", "wikipedia.org"]})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

Instead of polling, you can attach a webhook to the run (see Integrations). The API tab of this page shows the same calls for JavaScript and the Apify CLI.

#### More API examples

Synchronous:

```bash
curl -X POST "https://api.apify.com/v2/acts/sfoln~domain-intelligence/run-sync-get-dataset-items?token=<APIFY_TOKEN>&timeout=120" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["apify.com", "wikipedia.org"]}'
```

Asynchronous (large lists):

```bash
curl -X POST "https://api.apify.com/v2/acts/sfoln~domain-intelligence/runs?token=<APIFY_TOKEN>" \
  -H "Content-Type: application/json" -d @domains.json
## → poll /v2/actor-runs/<runId>, then GET /v2/datasets/<defaultDatasetId>/items?view=overview&format=csv
```

Python:

```python
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("sfoln/domain-intelligence").call(run_input={"domains": ["apify.com"]})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["domain"], item["registrar"], item["expiresAt"], item["mailProvider"], item["sslDaysUntilExpiry"])
```

### Integrations

- **Make, Zapier and n8n:** run `sfoln/domain-intelligence` with the Apify app (in n8n, the Apify node) and pass the dataset items to the next step of your workflow.
- **Google Sheets:** add the items to a sheet with the Google Sheets step of Make, Zapier or n8n, or download them as CSV or Excel from the run's Storage tab or the API (`format=csv` or `format=xlsx`).
- **Webhooks:** get a POST request when a run succeeds or fails: add a webhook in the Actor's Integrations tab, or pass `webhooks` when you start a run through the API.
- **Schedules:** run it hourly, daily or weekly with the same input from Apify Schedules; every run keeps its results in its own dataset.
- **More:** Slack, Google Drive, GitHub, LangChain, LlamaIndex and other [Apify integrations](https://docs.apify.com/platform/integrations).

### Related Actors

- [Website Audit – SEO, Security, SSL & DNS](https://apify.com/sfoln/website-audit)
- [Tech Stack Detector – Website Technologies](https://apify.com/sfoln/tech-stack-detector)
- [URL Safety Checker – Phishing & Malware](https://apify.com/sfoln/url-safety-check)
- [URL Metadata & Open Graph Extractor](https://apify.com/sfoln/url-metadata)
- [Company Enrichment – Firmographics by Domain](https://apify.com/sfoln/company-enrichment)
- [Website Contact Details Scraper](https://apify.com/sfoln/company-contacts-finder)

See [all our Actors](https://apify.com/sfoln) on Apify Store.

### FAQ

#### How much does it cost, and am I charged for failed items?

You pay per event at the prices under Pricing above, plus a run start fee of $0.00005 per GB of memory, with no subscription; platform usage is included. Errors and skipped items (`skipped_…` statuses) are never charged; a lookup that finds nothing is charged only where Pricing says so, and every record's `charged` field shows whether it was billed. Set **Maximum cost per run** to cap what a single run can spend.

#### How fresh is the data?

Each run queries its source while it runs, so you get what the source returns at that moment. Source-specific details, such as how often a provider refreshes its own database, are under Limits & notes.

#### What are the limits?

Per-run caps (for example the maximum number of items) are listed under Input, source limits under Limits & notes, and the free-plan limit under Pricing. For big batches, start the run asynchronously (see API usage) and read the dataset when it has finished.

#### Can I schedule runs or get results by webhook?

Yes. Create a schedule in Apify Console to run it hourly, daily or weekly with the same input, and add a webhook to get a POST request when a run finishes (see Integrations).

#### What does this Actor record about my runs?

This Actor records operational metrics about each run (item counts, duration, charges, error codes and a pseudonymous hash of your Apify user ID) to monitor reliability and costs. It never stores your input or results.

#### How is availability decided?

DNS first: a registrable domain that answers NXDOMAIN is a candidate, and the registry's RDAP service confirms it by having no record (HTTP 404). Then available is true. A domain that resolves or has an RDAP record is not available. When the registry does not answer (rate limits, ccTLDs without RDAP), or the name is outside the Public Suffix List, available is null. Unregistered domains are never charged.

#### Why is a subdomain returned as not_found?

When the host itself does not exist in DNS (for example a typo like shpo.example.com) while its registrable domain is registered, the record says so with error code host_not_found and is not charged. Look up the registrable domain to get its registration data.

#### How do I get help?

Open an issue in the Actor's Issues tab and include the link to the run, so we can reproduce the problem. Feature requests are welcome there too.

### Limits & notes

- Data sources are public protocols only: RDAP at the authoritative registry (located via the IANA bootstrap registry, rdap.org as fallback), DNS-over-HTTPS (Cloudflare resolver), a direct TLS handshake and one homepage request. Everything is fetched live at run time; nothing is cached between runs.
- Some registries, mostly country-code TLDs, do not offer RDAP: registration fields are then `null`, `sections.rdap` is `error` with a warning naming the TLD, and a name that does not resolve gets `registered: null` and `available: null` (status `error`, not charged), because a registered name on hold answers NXDOMAIN too. Some registries rate-limit RDAP, which can produce `rdap` warnings on very large single-TLD lists — lower `concurrency` in that case.
- The TLS and homepage checks only contact public addresses: when a domain resolves to a private, loopback, link-local or otherwise non-public IP they are `skipped` (with a warning), and homepage redirects are followed only to public hosts on ports 80/443.
- Registrant contact data is usually redacted under GDPR/registrar privacy and is not returned when redacted.
- Availability is a DNS + RDAP check, not a registrar quote: premium, reserved and blocked names can show `available: true` and still not be purchasable at standard prices.
- Not included: historical WHOIS, subdomain enumeration, port scans, blacklist/reputation checks, traffic or SEO metrics. For a full website audit (SEO, all security headers, broken links, performance) use Website Audit.

# Actor input Schema

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

Domain names (apify.com) or URLs (https://www.apify.com/store). The host is extracted, "www." removed and duplicates dropped. Registration data is looked up for the registrable domain. Accounts on the Apify free plan can process up to 10 domains per run.

## `checkTls` (type: `boolean`):

Open a TLS connection on port 443 and report issuer, validity dates, SANs and protocol.

## `checkHomepage` (type: `boolean`):

Fetch the homepage (https, falling back to http; 8 s timeout, first 512 KB) for HTTP status, redirects, final URL, title, security-header grade (HSTS, CSP, X-Frame-Options, …), favicon and detected technologies.

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

Safety cap on the number of domains processed in this run.

## `concurrency` (type: `integer`):

How many domains are analysed in parallel. Some registries rate-limit RDAP; lower this for very large lists of one TLD.

## `includeRaw` (type: `boolean`):

Attach the unmodified RDAP JSON to each record as raw.rdap (larger output).

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "wikipedia.org"
  ],
  "checkTls": true,
  "checkHomepage": true,
  "maxDomains": 1000,
  "concurrency": 5,
  "includeRaw": false
}
```

# Actor output Schema

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

No description

## `summary` (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",
        "wikipedia.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("sfoln/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": [
        "apify.com",
        "wikipedia.org",
    ] }

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

```

## MCP server setup

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