# WHOIS Lookup & DNS Checker – Domain Age, SPF, DMARC, SSL (`rowfeed/domain-rdap-checker`) Actor

Bulk WHOIS lookup via official RDAP plus DNS checker: domain age, expiry, registrar, nameservers, DNS records, SSL expiry and an email security grade A-F (SPF, DMARC, DKIM) with plain-English issues. Never WHOIS contact data.

- **URL**: https://apify.com/rowfeed/domain-rdap-checker.md
- **Developed by:** [Rowfeed](https://apify.com/rowfeed) (community)
- **Categories:** Developer tools, Automation
- **Features:** Limited permissions
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 domains

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

Check how old a domain is, whether it is registered, when it expires, who the registrar is, how its DNS and
SSL are set up, and how well its email is protected against spoofing (SPF, DMARC, DKIM, graded A to F) - straight from the official RDAP registries, DNS-over-HTTPS, and a direct TLS
handshake. Built for scripts, dashboards, portfolio monitors and AI agents that need a clean,
typed answer, not a scraped WHOIS text blob.

**No WHOIS contact data, ever.** Only the registrar - a company - comes out of this Actor.

### What you get

- **Domain age** - `domain_age_years` (e.g. 19.1) from the registration date, for bulk domain age checks.
- **Registration status and expiry** - `is_registered`, `expires_at`, `days_until_expiry` (negative
  once a domain has lapsed), `created_at`, `updated_at`, EPP `statuses`, and which registry RDAP
  server actually answered (`rdap_server`).
- **Registrar, nameservers, DNSSEC** - the registrar company name and IANA Registrar ID, the
  domain's nameservers, and whether DNSSEC is signed.
- **DNS and mail posture** - A/MX/NS/TXT records over Cloudflare DNS-over-HTTPS, the mail provider
  inferred from MX hostnames (Google Workspace, Microsoft 365, Proofpoint, ...), and whether SPF
  and DMARC are present. Optionally, live TLS certificate issuer and expiry.

### Email security check: SPF, DMARC and DKIM, graded A to F

With DNS lookups on (the default), every registered domain gets a `mail_auth` block at no extra cost (example: nrk.no, 05.11.2026). Use it as a bulk
DMARC checker, SPF checker or DKIM checker for client audits, deliverability work, sales prospecting or a security review.

```json
"mail_auth": {
  "grade": "C",
  "issues": ["DMARC policy is p=none: spoofed mail is only reported, not blocked"],
  "spf_record": "v=spf1 mx a ip4:160.67.135.178 ... include:spf.protection.outlook.com include:_spf.google.com -all",
  "spf_all": "-all",
  "spf_lookup_terms": 7,
  "dmarc_record": "v=DMARC1; p=none; pct=100; rua=mailto:dmarc-reports@nrk.no; ...",
  "dmarc_policy": "none",
  "dmarc_subdomain_policy": null,
  "dmarc_pct": 100,
  "dmarc_reports": true,
  "dkim_selector": "google"
}
```

| Grade | DMARC | SPF |
|---|---|---|
| A | `p=quarantine` or `p=reject` on 100 % of mail, with reports (`rua`) | good |
| B | enforced, but no reports, `pct` below 100, or SPF weak or missing | any |
| C | `p=none` (spoofing is reported, not blocked) | good |
| D | `p=none` with weak or missing SPF, or DMARC missing/duplicated with good SPF | |
| F | missing or duplicated | weak or missing |

Good SPF = exactly one record that ends in `-all` or `~all` (or hands over with `redirect=`) and has at most 10 lookup
terms. Weak SPF = `+all`, `?all`, no `all`, or too many lookups. Several SPF records count as missing. A DMARC record
without a valid `p=` counts as `p=none`, as the DMARC standard says. If a DNS lookup fails, `mail_auth` is `null`
(not graded) rather than a wrong grade.

`issues` lists each problem found, in plain English. Measured 05.11.2026: microsoft.com and github.com get A, nrk.no gets C
(`p=none`), and a domain with neither record gets F.

DKIM keys are published under a "selector" that cannot be listed, so the Actor tries the selectors of the mail
service it detects (Google Workspace, Microsoft 365) or two common defaults. `dkim_selector` shows the one found. `null` means
none of those was found, which does not prove that DKIM is missing, so DKIM never lowers the grade.
`spf_lookup_terms` counts the lookups in the top-level SPF record only. Nested includes add more.

### Why RDAP instead of WHOIS

RDAP is the IETF's structured, machine-readable replacement for WHOIS, served directly by the
registries (`rdap.org` resolves and redirects to the authoritative server for each TLD). WHOIS
scrapers parse inconsistent free text and, along the way, usually pick up registrant/admin/tech
contact details. This Actor never fetches or forwards those fields: the fetch layer's registrar
extractor only ever reads the entity whose role is `"registrar"`, and only ever takes its company
name and IANA ID from that entity - never a registrant, admin or tech contact, never an email or
phone number of a named individual, and never the registrar's own nested abuse-contact sub-entity.
That is deliberate: privacy by design, and the reason this tool is cheaper and cleaner to build a
product on top of than a service that resells WHOIS contacts.

### Example output row

One real row from a default run (`github.com`, 2026-09-23; the `TXT` array is trimmed here for
readability - the real dataset row has all of them):

```json
{
  "domain": "github.com",
  "tld": "com",
  "is_registered": true,
  "registrar_name": "MarkMonitor Inc.",
  "registrar_iana_id": "292",
  "statuses": ["client delete prohibited", "client transfer prohibited", "client update prohibited"],
  "created_at": "2007-10-09T18:20:50Z",
  "updated_at": "2026-09-07T09:22:52Z",
  "expires_at": "2028-10-09T18:20:50Z",
  "days_until_expiry": 747,
  "domain_age_years": 19.1,
  "nameservers": [
    "DNS1.P08.NSONE.NET", "DNS2.P08.NSONE.NET", "DNS3.P08.NSONE.NET", "DNS4.P08.NSONE.NET",
    "NS-1283.AWSDNS-32.ORG", "NS-1707.AWSDNS-21.CO.UK", "NS-421.AWSDNS-52.COM", "NS-520.AWSDNS-01.NET"
  ],
  "dnssec": false,
  "rdap_server": "rdap.verisign.com",
  "dns": {
    "A": ["140.82.121.4"],
    "MX": ["0 github-com.mail.protection.outlook.com."],
    "NS": ["dns1.p08.nsone.net.", "dns2.p08.nsone.net.", "..."],
    "TXT": ["\"google-site-verification=...\"", "\"v=spf1 ip4:192.30.252.0/22 ... ~all\"", "..."]
  },
  "mail_provider": "Microsoft 365",
  "spf_present": true,
  "dmarc_present": true,
  "checked_at": "2026-09-23T11:35:20+00:00"
}
```

An unregistered domain gives a clean, non-error row instead of a crash or a false positive:

```json
{
  "domain": "zzz-not-a-real-domain-98765.com",
  "tld": "com",
  "is_registered": false,
  "registrar_name": null,
  "rdap_server": "rdap.verisign.com",
  "...": "every other RDAP field is null/empty, no `error` field"
}
```

`is_registered: false` is a normal, common answer, not an error - it means the registry itself
said the domain does not exist. If instead an RDAP server is unreachable or times out, you get a
genuinely different row: `{"domain": ..., "error": "network", "errorMessage": "..."}`. The two are
never conflated, so a downstream buyer can trust `is_registered: false` as ground truth.

#### Three answers, never two

Some top-level domains publish no RDAP service at all - `.mil` and a handful of others. For those,
nobody can tell you whether a domain is registered, so this Actor says exactly that instead of
guessing. `.io`, `.sh` and `.ac` are a special case: IANA's RDAP list leaves them out, so most tools
report them as unknown or, worse, as free - but their registry does answer RDAP, and this Actor asks
it directly, so you get the real registrar, expiry date and nameservers. Every row carries one of
three answers:

| `rdap_status` | `is_registered` | What it means |
|---|---|---|
| `registered` | `true` | The registry returned the domain record |
| `not_registered` | `false` | The registry said the domain does not exist |
| `no_rdap_service` | `null` | The TLD publishes no RDAP service, so registration is unknown |

DNS records, mail provider, SPF, DMARC and the TLS certificate are still returned in the
`no_rdap_service` case, because those come from other sources and are just as real.

This matters more than it sounds: a tool that trusts the IANA list either says every `.io` domain is
unknown or tells you a live, paid-for `.io` domain is free. Check any `.io` domain here and compare.

You can download the dataset in various formats such as JSON, CSV, or Excel from the Apify
Console, or pull it through the API/integrations.

### Input

- **Domains** (`domains`) - bare domains or full URLs (`github.com`, `https://github.com/x`); the
  scheme, path, query and port are stripped automatically. Default: `github.com`, `apify.com`,
  `example.org`.
- **Max domains** (`maxDomains`) - cap on rows checked, 1-1000. Default 100.
- **Include DNS lookups** (`includeDns`) - resolve the record types below plus a dedicated
  `_dmarc.<domain>` TXT lookup (plus a few DKIM selector lookups when the domain has MX records), and
  add `mail_provider`, `spf_present`, `dmarc_present` and the graded `mail_auth` block. Free, part of
  the `domain` event. Default on.
- **DNS record types** (`dnsTypes`) - which record types to resolve. Default `A`, `MX`, `NS`, `TXT`.
- **Include TLS certificate check** (`includeTls`) - open a TLS connection to port 443 and read the
  certificate's issuer and expiry. One extra `tls-check` event per successful check. Default off.

The default input runs with zero edits and returns three real, valuable rows.

### Pricing

Pay-per-event:

- **Actor start** - $0.001, once per run.
- **Domain** - $0.0025 per domain row (registration, expiry, registrar, DNS, SPF/DMARC/DKIM grade).
- **TLS check** - $0.001, only when `includeTls` is on and a certificate was actually read.

$2.50 per 1,000 domains checked - no charge for domains that turn out to be unregistered failing
silently, and no charge for a WHOIS-contacts add-on you didn't ask for, because there isn't one.

### Reliability and rate limits

Requests stay under ~5/second overall. 429s and 5xx/network errors retry with exponential backoff
(429 honours `Retry-After`); 401/403 and other unexpected responses are reported once, not retried.
Every failure is counted by category (`network`, `rate_limit`, `blocked`, `other`) in the run's
`STATS` key-value record, separately from `not_found`/unregistered-domain counts, so you can tell
input problems (typo'd domains) apart from source outages at a glance.

### More SEO tools from Rowfeed

- [Website Email Extractor & Contact Finder](https://apify.com/rowfeed/website-contact-finder) – company emails and phones from the same domains.
- [Subdomain Finder & Scanner](https://apify.com/rowfeed/subdomain-finder) – every subdomain of a domain from certificate logs and passive DNS, with a live check.
- [Website Technology Checker](https://apify.com/rowfeed/website-technology-checker) – CMS, analytics, ecommerce platform and 160+ other technologies any website uses.
- [SEO Audit Tool & Website Crawler](https://apify.com/rowfeed/seo-site-audit) – titles, meta descriptions, H1, noindex, broken links and a 0-100 SEO score for every page.
- [Keyword Research Tool](https://apify.com/rowfeed/keyword-search-volume) – search volume, CPC, keyword difficulty, intent and keyword ideas (Google & Bing).
- [Competitor Keyword Research](https://apify.com/rowfeed/competitor-keywords) – every Google keyword any website ranks for, with position, volume and traffic.
- [SERP API & Rank Checker](https://apify.com/rowfeed/google-serp-api) – Google results for any keyword and country, with your domain's position.
- [Backlinks Checker](https://apify.com/rowfeed/backlink-checker) – referring domains, anchors, dofollow and link strength of any site.
- [Website Traffic & Domain Authority Checker](https://apify.com/rowfeed/website-traffic-checker) – traffic estimate and authority for up to 10,000 domains.
- [Broken Link Checker](https://apify.com/rowfeed/broken-link-checker) – 404s, dead links, broken images and redirect chains on any website.
- [AI Search Visibility Tracker](https://apify.com/rowfeed/ai-search-visibility-tracker) – is your brand named and cited by ChatGPT, Gemini, Perplexity and Google AI Mode.
- [Local Rank Tracker](https://apify.com/rowfeed/google-maps-rank-tracker) – Google Maps rankings on a geo-grid around a business.
- [Google Trends API](https://apify.com/rowfeed/google-trends-explorer) – interest over time, regions and rising searches.
- [Google Images API](https://apify.com/rowfeed/google-images-api) – image URLs and the pages they are on, for image SEO.
- [Sitemap URL Extractor](https://apify.com/rowfeed/sitemap-url-extractor) – every URL in a site's sitemaps, with lastmod.
- [PageSpeed Insights API](https://apify.com/rowfeed/pagespeed-insights-api) – Lighthouse scores, Core Web Vitals and real-user speed data for many pages at once.
- [DA PA Checker](https://apify.com/rowfeed/da-pa-checker) – domain authority, page authority and spam score for up to 10,000 domains or URLs.

# Actor input Schema

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

Bare domains or full URLs to check, e.g. "github.com" or "https://github.com/some/path". Scheme, path, query and port are stripped automatically.

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

Maximum number of domain rows to check in this run. Each row is one `domain` event ($2.50 per 1,000).

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

Look up the record types in "DNS record types" over Cloudflare DNS-over-HTTPS, plus a dedicated \_dmarc TXT lookup, and infer mail_provider/spf_present/dmarc_present. Free - part of the domain event, no extra charge.

## `dnsTypes` (type: `array`):

DNS record types to resolve for each domain when "Include DNS lookups" is on.

## `includeTls` (type: `boolean`):

Open a TLS connection to port 443 of each registered domain and read the certificate's expiry date and issuer. Adds one connection per domain and one `tls-check` event ($0.001) per successful check.

## Actor input object example

```json
{
  "domains": [
    "github.com"
  ],
  "maxDomains": 100,
  "includeDns": true,
  "dnsTypes": [
    "A",
    "MX",
    "NS",
    "TXT"
  ],
  "includeTls": false
}
```

# Actor output Schema

## `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": [
        "github.com",
        "apify.com",
        "example.org"
    ],
    "dnsTypes": [
        "A",
        "MX",
        "NS",
        "TXT"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("rowfeed/domain-rdap-checker").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": [
        "github.com",
        "apify.com",
        "example.org",
    ],
    "dnsTypes": [
        "A",
        "MX",
        "NS",
        "TXT",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("rowfeed/domain-rdap-checker").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": [
    "github.com",
    "apify.com",
    "example.org"
  ],
  "dnsTypes": [
    "A",
    "MX",
    "NS",
    "TXT"
  ]
}' |
apify call rowfeed/domain-rdap-checker --silent --output-dataset

```

## MCP server setup

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

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/XXOc9q2hgzya03Qs1/builds/vpva2xC0Dlc5YuUgK/openapi.json
