Bulk Domain Intelligence: WHOIS/RDAP, DNS, SSL, Availability avatar

Bulk Domain Intelligence: WHOIS/RDAP, DNS, SSL, Availability

Pricing

Pay per event

Go to Apify Store
Bulk Domain Intelligence: WHOIS/RDAP, DNS, SSL, Availability

Bulk Domain Intelligence: WHOIS/RDAP, DNS, SSL, Availability

Bulk domain lookup: RDAP/WHOIS registration data, DNS records, SSL certificate and registration availability for up to 100k domains in one normalized JSON per domain. Failed lookups are never charged.

Pricing

Pay per event

Rating

0.0

(0)

Developer

cottony_brittlegill

cottony_brittlegill

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

15 hours ago

Last modified

Categories

Share

Domain Intelligence Bulk — WHOIS/RDAP + DNS + SSL + Availability in one JSON

Check thousands of domains in one run: registration data (registrar, creation, expiry), DNS records, an SSL certificate check and domain availability — one normalized JSON per domain. A bulk WHOIS API and RDAP client, a DNS records lookup and a domain expiry checker, combined, so you don't have to stitch three tools together. Up to 100,000 domains per run — see "Honest limits" for the throughput and default timeout you should actually expect on a run that large.

What you get per domain

Every dataset item is a single DomainReport:

FieldTypeMeaning
domainstringNormalized hostname you queried (punycode if IDN)
registrableDomainstringeTLD+1 actually queried against the registry
tldstringPublic suffix (e.g. com, co.uk)
statusregistered | likely_available | unknownSee "Honest limits" below
registration.sourcerdap | whois | nullWhich protocol answered
registration.registrarstring | nullRegistrar name, when the source exposes it
registration.createdAt / updatedAt / expiresAtISO date | nullRegistration dates
registration.daysToExpirynumber | nullDays until expiresAt, computed at check time — the domain-expiry-tracking signal
registration.domainStatusstring[]EPP status codes (RDAP only)
registration.nameserversFromRegistrystring[]Nameservers as declared at the registry. Always populated on RDAP. On WHOIS fallback, only when the reply uses a per-line label our parser recognizes (e.g. DENIC's Nserver:) — dialects that list nameservers under a section header instead (e.g. .it's Nameservers block) still come back [], same parser-gap situation as the .it registrar field below
dnsobject | nullNS, A, AAAA, MX, TXT, CNAME records (null if you disable the signal)
sslobject | nullSSL/TLS certificate check: issuer, subject CN, SAN, validity window, days to expiry
errorsstring[]Non-fatal per-signal errors (a DNS timeout doesn't fail the whole report)
checkedAtISO dateWhen the check ran
chargeablebooleantrue unless status is unknown — whether this report qualifies for billing
chargedbooleanWhether the Apify platform actually billed this report (see Pricing)

Real output for example.com (this run's .env/network conditions — dates and counts will differ on your own run):

{
"domain": "example.com",
"registrableDomain": "example.com",
"tld": "com",
"status": "registered",
"registration": {
"source": "rdap",
"registrar": "RESERVED-Internet Assigned Numbers Authority",
"createdAt": "1995-08-14T04:00:00Z",
"updatedAt": "2026-01-16T18:26:50Z",
"expiresAt": "2026-08-13T04:00:00Z",
"daysToExpiry": 15,
"domainStatus": ["client delete prohibited", "client transfer prohibited", "client update prohibited"],
"nameserversFromRegistry": ["elliott.ns.cloudflare.com", "hera.ns.cloudflare.com"]
},
"dns": {
"ns": ["hera.ns.cloudflare.com", "elliott.ns.cloudflare.com"],
"a": ["104.20.23.154", "172.66.147.243"],
"aaaa": ["2606:4700:10::ac42:93f3", "2606:4700:10::6814:179a"],
"mx": [{ "priority": 0, "exchange": "" }],
"txt": ["_k2n1y4vw3qtb4skdx9e7dxt97qrmmq9", "v=spf1 -all"],
"cname": [],
"error": null
},
"ssl": {
"issuer": "SSL Corporation",
"subjectCN": "example.com",
"validFrom": "2026-05-31T21:39:12.000Z",
"validTo": "2026-08-29T21:41:26.000Z",
"daysToExpiry": 32,
"san": ["example.com", "*.example.com"],
"error": null
},
"errors": [],
"checkedAt": "2026-07-28T11:51:38.401Z",
"chargeable": true,
"charged": false
}

(charged: false here because this example was run locally, outside Apify's Pay-Per-Event ledger — see Pricing.)

Why this actor

  • 4 signals in 1 run (others give you only WHOIS, only DNS, or only availability)
  • RDAP-first (the modern registry protocol), WHOIS only as fallback — real bulk WHOIS API + RDAP coverage across gTLDs and ccTLDs, not just .com
  • Registry rate limits respected (per-registry throttling, RFC 7480 backoff)
  • SSRF/DNS-rebinding hardened SSL check — the TLS lookup resolves the host, blocks private, loopback, link-local and cloud-metadata addresses (RFC1918, 127.0.0.0/8, 169.254.0.0/16, etc.), and connects to the validated IP rather than re-resolving at connect time, so pointing this actor at attacker-controlled or untrusted domain input can't be used to reach your internal network
  • Failed lookups are never charged — every report is pushed with an honest chargeable flag (false whenever status is unknown), and charged reflects what the Apify platform's own billing ledger actually billed, not merely what we attempted — you pay only for delivered reports
  • Pay-per-event pricing, agent-friendly (x402-eligible): no subscription, no per-run minimum, callable straight from an API key or an agent's wallet

Honest limits (read before buying)

  • No registrant email/name. GDPR-redacted WHOIS/RDAP since 2018. Registrar, dates and nameservers: yes. Personal contact data: no — from anyone, on any TLD. If a tool promises registrant emails from public WHOIS in 2026, be skeptical of it, not of us.
  • Some ccTLDs have no RDAP at all (confirmed directly against IANA's live bootstrap registry: neither .de nor .it are listed) and fall back to WHOIS:43. WHOIS formats vary a lot registry to registry: .de (DENIC) actually replies with six lines — Domain:, Domain-Ace:, up to three Nserver: lines, Status: and Changed: (a last-modification timestamp) — confirmed with a live query. We parse Changed: into updatedAt and the Nserver: lines into nameserversFromRegistry, but DENIC's reply never includes a registrar name or creation/expiry dates, so those three stay null — that part genuinely is the registry's own minimal record, not a parser gap. IDN (umlaut) .de domains are queried with DENIC's documented -T dn,ace flag so they resolve correctly to the real registry answer (Status: connect/Status: free), not a malformed-query artifact. .it (Registro.it) replies with a fuller record — creation, last-update and expiry dates. It does include a registrar section (Registrar / Organization: ...) and a Nameservers section, but our parser doesn't yet handle that section-header layout (no per-line Registrar:/Nserver: label to match), so registrar and nameserversFromRegistry currently come back null/[] for .it — a parser gap, not a registry limitation; it's on the list to fix, not being passed off as "the registry doesn't say". In all cases you get status: "registered" with whatever fields we could actually extract — unparsed/unsupported fields are reported as null/[], never guessed or backfilled.
  • When a registry's reply can't be interpreted at all (neither a known "found" nor a known "not found" shape), status is "unknown" with an explicit message in errors (e.g.
    "unparseable whois response for <tld>"
    ) — never silently reported as available or registered.
  • "likely_available" is a strong signal (registry 404/"available" answer + zero nameservers), not a guarantee — always confirm at your registrar before buying a name.
  • Large single-TLD lists are throttled per registry, on purpose — but raising perTldConcurrency is not the way to speed that up. Concurrency to any one registry defaults to 3 lookups at a time (per the input's perTldConcurrency), regardless of how many domains in your batch share that registry — multi-label ccTLDs like .co.uk count against the same budget as .uk, since they're the same registry under the hood. This respects RFC 7480 and keeps registries from rate-limiting or blocking us (and you). What it does not do is throttle the DNS and SSL/TLS checks that run alongside it — those are governed only by globalConcurrency. Measured live, on 51 fast, healthy .com domains with default settings (perTldConcurrency=3, globalConcurrency=20, DNS+SSL on): 115 ms/domain. Raising globalConcurrency to its maximum (50) gave 24 ms/domain — a 5x speedup — while perTldConcurrency barely moves the number once DNS/SSL are enabled, because the registry call is rarely the bottleneck. If you need a large run to finish faster, raise globalConcurrency, not perTldConcurrency. Expect throughput in the 100–150 ms/domain range at the defaults (real prospect/portfolio lists — slower DNS, expired certs, dead hosts eating the full lookupTimeoutMs, default 10s — will run slower than this best-case healthy-.com measurement, not faster). At that rate a 100,000-domain run is realistically 2.5–4 hours at default settings, or well under an hour with globalConcurrency raised toward its maximum. The actor's own platform run timeout defaults to 6 hours (timeoutSecs: 21600) specifically to give a default-settings 100k run room to finish rather than being killed mid-flight with charges already incurred standing — but a very slow, mostly-unresponsive list can still exceed it. For predictable large runs: split single-TLD batches across runs, or raise globalConcurrency.
  • The domain cap is enforced, not just documented. domains + domainsText are capped at 100,000 combined; anything past that is dropped before processing (never charged, never returned) and a warning naming the submitted/kept/dropped counts is written to the run log — so a run over the cap is loud about it instead of silently returning a shorter dataset than you submitted.
  • A leading www. is stripped and deduplicated with the apex domain by default. www.example.com and example.com in the same batch are treated as one domain (one domain-report charge, one dataset row) — set keepWww: true if you specifically need the www host's own DNS/TLS data checked separately from the apex (it can genuinely differ, e.g. a different SSL certificate or CNAME target).
  • No claim of "cheapest domain intelligence API" and no domain-monitoring subscription pitch here: this is a bulk lookup actor, run on demand, for what it actually measures — registration, DNS, SSL, availability — priced per result, not per month.

Pricing

Pay-Per-Event (PPE) — no subscription:

EventPriceWhen it fires
actor-start$0.005Once per run
domain-report$0.002Once per domain report with chargeable: true (status other than unknown)

$2.00 per 1,000 domains (domain-report events) plus a flat $0.005 per run. Unparsed/unresolvable domains (status: "unknown") are never billed — chargeable and charged are pushed on every dataset item so you can reconcile your own invoice against the data.

Use cases

1. Domain portfolio expiry audit. Feed your whole portfolio (hundreds or thousands of domains across TLDs) in one run and get daysToExpiry for every one, in a single normalized JSON — instead of clicking through each registrar's dashboard or juggling five WHOIS CLIs for five different TLD formats.

2. Lead-gen / sales tech enrichment. Given a prospect list of company domains, enrich each with registrar, DNS provider (nameservers reveal Cloudflare/AWS/etc.), MX provider (Google Workspace vs. Microsoft 365 vs. self-hosted) and SSL issuer — firmographic and tech-stack signal for outbound targeting, at $2 per 1,000 domains.

3. Agent pipelines via API / x402. An AI agent doing domain due-diligence before recommending or registering a name calls this actor directly — pay-per-event pricing means no dashboard, no subscription, just an API call (or an x402 payment) per batch.

Standard Apify API call (synchronous, returns the dataset items directly):

curl -X POST \
"https://api.apify.com/v2/acts/YOUR_USERNAME~domain-intelligence-bulk/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"domains": ["example.com", "nic.it", "münchen.de"],
"includeDns": true,
"includeSsl": true,
"whoisFallback": true
}'

FAQ

What's the difference between WHOIS and RDAP? RDAP is the modern, structured (JSON) successor to the old plain-text WHOIS protocol — it returns more consistent field names across registries instead of a different free-text layout per registrar. This actor queries RDAP first and only falls back to WHOIS:43 for TLDs that don't offer RDAP yet (confirmed directly against IANA's live RDAP bootstrap registry — see "Honest limits" for exactly which ones).

Can I get a domain's owner name or email from this? No — and no tool legitimately can, from public WHOIS/RDAP, since the 2018 GDPR redactions. Registrar, dates, nameservers and status codes: yes. Registrant contact data: no, from anyone, on any TLD.

How do I check if a domain is available before buying it? status: "likely_available" is a strong signal (a registry not-found/ available answer plus zero nameservers), not a guarantee — always confirm availability at a registrar before purchasing.

Does www.example.com and example.com count as two domains? No, by default. A leading www. is stripped and deduplicated with the apex domain — one domain-report charge, one dataset row — unless you set keepWww: true because you specifically need the www host's own DNS/TLS data checked separately.

Input / Output

Input

{
"domains": ["example.com", "google.com"],
"domainsText": "",
"keepWww": false,
"includeDns": true,
"includeSsl": true,
"whoisFallback": true,
"perTldConcurrency": 3,
"globalConcurrency": 20,
"lookupTimeoutMs": 10000
}
FieldTypeDefaultDescription
domainsstring[]["example.com", "google.com"]Domains to analyze. Bare domains or full URLs; scheme/www/path stripped automatically, so www.example.com and example.com count and dedupe as one domain. Combined with domainsText, capped at 100,000 — anything past the cap is dropped before processing, with a warning logged naming the submitted/kept/dropped counts.
domainsTextstringAlternative/additional domains pasted as newline- or comma-separated text; merged with domains (same 100,000 combined cap).
keepWwwbooleanfalseKeep the www host as its own domain instead of stripping/deduplicating it with the apex — use when the www host's own DNS/TLS data genuinely needs to be checked separately.
includeDnsbooleantrueInclude NS, A, AAAA, MX, TXT, CNAME records in the output. The DNS lookup itself always runs regardless of this flag (it's also used internally to detect likely_available domains) — this only controls whether the dns object appears in the output, not whether it's fetched, so turning it off does not reduce runtime.
includeSslbooleantrueInclude the SSL/TLS certificate check (issuer, SAN, validity, days to expiry).
whoisFallbackbooleantrueFall back to WHOIS:43 for TLDs without RDAP.
perTldConcurrencyinteger3Concurrent lookups per registry — kept low on purpose to respect registry rate limits (RFC 7480). Does not throttle DNS/SSL, so raising it does little for overall speed once those are enabled.
globalConcurrencyinteger20Overall concurrent domains in flight — the effective throughput knob for full-signal runs. Measured ~5x faster (115 ms/domain → 24 ms/domain) going from the default to the maximum (see "Honest limits").
lookupTimeoutMsinteger10000Per-lookup timeout in milliseconds.

Output (dataset items)

One DomainReport per domain (see field table above), in the order resolved (not necessarily input order, since domains run concurrently). Three real examples from a live run — a clean .com, an available .com, and a .de domain resolved through the WHOIS fallback with sparse fields (the honest ccTLD case described above):

{
"domain": "this-domain-should-not-exist-x9z7q.com",
"registrableDomain": "this-domain-should-not-exist-x9z7q.com",
"tld": "com",
"status": "likely_available",
"registration": {
"source": null, "registrar": null, "createdAt": null, "updatedAt": null,
"expiresAt": null, "daysToExpiry": null, "domainStatus": [], "nameserversFromRegistry": []
},
"dns": { "ns": [], "a": [], "aaaa": [], "mx": [], "txt": [], "cname": [], "error": null },
"ssl": {
"issuer": null, "subjectCN": null, "validFrom": null, "validTo": null,
"daysToExpiry": null, "san": [], "error": "tls getaddrinfo ENOTFOUND this-domain-should-not-exist-x9z7q.com"
},
"errors": ["tls getaddrinfo ENOTFOUND this-domain-should-not-exist-x9z7q.com"],
"checkedAt": "2026-07-28T11:51:38.422Z",
"chargeable": false,
"charged": false
}
{
"domain": "xn--mnchen-3ya.de",
"registrableDomain": "xn--mnchen-3ya.de",
"tld": "de",
"status": "registered",
"registration": {
"source": "whois", "registrar": null, "createdAt": null, "updatedAt": "2025-05-15T13:42:32.000Z",
"expiresAt": null, "daysToExpiry": null, "domainStatus": [],
"nameserversFromRegistry": ["ns01e.muenchen.de", "ns02e.muenchen.de", "ns04e.muenchen.de"]
},
"dns": {
"ns": ["ns04e.muenchen.de", "ns02e.muenchen.de", "ns01e.muenchen.de"],
"a": ["194.246.166.100"], "aaaa": ["2a00:1830:a001:f00a::100"],
"mx": [], "txt": [], "cname": [], "error": null
},
"ssl": {
"issuer": "Let's Encrypt", "subjectCN": "www.xn--mnchen-3ya.de",
"validFrom": "2026-06-26T22:49:29.000Z", "validTo": "2026-09-24T22:49:28.000Z",
"daysToExpiry": 58, "san": ["www.xn--mnchen-3ya.de", "xn--mnchen-3ya.de"], "error": null
},
"errors": [],
"checkedAt": "2026-07-28T11:51:38.614Z",
"chargeable": true,
"charged": false
}

Note registrar/createdAt/expiresAt are still null for the .de domain above even though status is "registered" — DENIC's WHOIS reply genuinely never includes a registrar name or creation/expiry dates, reported honestly rather than papered over. updatedAt and nameserversFromRegistry, by contrast, are populated — DENIC's Changed:/Nserver: lines are parsed (see "Honest limits").

{
"domain": "nic.it",
"registrableDomain": "nic.it",
"tld": "it",
"status": "registered",
"registration": {
"source": "whois", "registrar": null,
"createdAt": "1997-08-26T22:00:00.000Z",
"updatedAt": "2026-01-15T23:46:30.000Z",
"expiresAt": "2026-12-31T00:00:00.000Z",
"daysToExpiry": 155, "domainStatus": [], "nameserversFromRegistry": []
},
"dns": {
"ns": ["dns.nic.it", "r.dns.it", "m.dns.it", "a.dns.it", "v.dns.it", "nameserver.cnr.it"],
"a": ["192.12.192.40"], "aaaa": ["2a00:d40:1:1:192:12:192:40"],
"mx": [{ "priority": 10, "exchange": "mail.nic.it" }],
"txt": ["HARICA-H1dSHZOI5GbN6mRuTtw", "facebook-domain-verification=p6h10ymlbgvexgwmhi9ufw2bc2d7k8", "v=spf1 ip4:192.12.192.0/23 ip6:2a00:d40::/32 ip4:192.12.194.0/24 a:*.nic.it ~all"],
"cname": [], "error": null
},
"ssl": {
"issuer": "Let's Encrypt", "subjectCN": "www.nic.it",
"validFrom": "2026-06-16T10:15:34.000Z", "validTo": "2026-09-14T10:15:33.000Z",
"daysToExpiry": 47,
"san": ["dotitalianregistry.it", "dotitregistry.it", "nic.it", "registro.it", "registrodominitalia.it", "registroitaliadomini.it", "web-r4.nic.it", "www.dotitalianregistry.it", "www.dotitregistry.it", "www.nic.it", "www.registro.it", "www.registrodominitalia.it", "www.registroitaliadomini.it"],
"error": null
},
"errors": [],
"checkedAt": "2026-07-28T11:51:38.620Z",
"chargeable": true,
"charged": false
}

Part of the same domain-tooling family, same pay-per-event pricing, same "never charge a failed lookup" policy:

  • Domain Mail-Health Bulk — SPF, DMARC, DKIM and MX audit with an A-F grade. Use it when the question is "can mail sent from this domain be trusted," not "who owns it or is it up."
  • Typosquat & Lookalike Domain Detector — finds lookalike/typosquatted domains registered against a brand you own and classifies which ones look like active threats versus parked inventory. Use it when you're monitoring for impersonation of your brand, not checking domains you already have in hand.