WHOIS Lookup, DNS Records Scraper - DMARC, SPF, DKIM Checker
Pricing
from $5.90 / 1,000 domain checkeds
WHOIS Lookup, DNS Records Scraper - DMARC, SPF, DKIM Checker
Bulk domain lookup in one run: WHOIS and RDAP registrar with registration and expiry dates, DNS records including A, AAAA, MX, NS, TXT, CAA, and the SSL certificate issuer with days until expiry. Email security reads SPF, DKIM and DMARC. 1200 zones over RDAP plus 238 more over WHOIS port 43.
Pricing
from $5.90 / 1,000 domain checkeds
Rating
0.0
(0)
Developer
Snow Leo Data
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Share
DNS Records API — Bulk Domain Lookup, Email Deliverability
Give it a list of domains. Get back one row per domain containing the DNS zone, the full email-authentication posture, the registration record and the TLS certificate — 80 fields, checked against the standards rather than merely copied out of the resolver.
Run it once and it is a bulk lookup. Run it on a schedule with monitor mode on and it becomes a drift alert: only the domains whose records actually moved come back, each carrying the list of fields that changed and what they used to be.
Everything comes from public sources reached with plain HTTPS, DNS-over-HTTPS and WHOIS on port 43. No API keys, no proxies, no browser, no third-party paid feed.
What it collects
What DNS records does it return?
Seven record types for every domain — A, AAAA, MX, NS, TXT, SOA,
CAA — plus the CNAME chain, reverse DNS on request, and the DNSSEC delegation.
The CNAME chain is kept separate from the address records, and that is not a
cosmetic decision. Asking a resolver for the A record of a www host usually
returns the chain first: measured on 12 September 2026, www.github.com answers
with [CNAME, A] and www.airbnb.com with two CNAMEs followed by nine
addresses. Any tool that reads the first answer and calls it an address prints
github.com in the column marked "IP". Here cname_chain holds the aliases and
a_records holds addresses only.
TXT records are reassembled correctly. A TXT string longer than 255 bytes is
stored in DNS as several character-strings, and resolvers disagree about how to
show them: on google._domainkey.apify.com, Google returns 408 characters with
no quotes while Cloudflare returns 413 characters containing four quotes — the
same key in two chunks. Stripping the quotes naively inserts a space in the
middle of the base64 public key, which produces a DKIM key that looks right,
has almost the right length, and is worthless. This Actor concatenates the chunks
with nothing between them, the way DNS itself does.
How is the SPF record checked?
Not by printing it. SPF is resolved through its entire include tree and the
number of DNS lookups it forces on the receiving server is counted against the
limit of 10 set by RFC 7208 §4.6.4. Past that limit the record is a permanent
error and receivers ignore it altogether — the domain's mail starts failing SPF
with no error message to anybody.
Measured on the Tranco top 300 on 12 September 2026: five domains (1.7 %) are already over the limit, the worst of them needing 24 lookups. Median lookup count is 2, the 90th percentile is 8. Two domains publish more than one SPF record, which RFC 7208 §3.2 also makes a permanent error. None of that is visible in the record text, which is exactly why tools that only show the text report those domains as fine.
The row carries spf_dns_lookups, spf_lookup_limit,
spf_lookup_limit_exceeded, the flattened spf_includes list, the all
qualifier, and spf_senders — the third-party services allowed to send mail for
the domain, recognised from the include tree.
How are DMARC, DKIM and the rest reported?
DMARC is parsed into fields, not left as a string: dmarc_policy,
dmarc_subdomain_policy, dmarc_percent, dmarc_rua, dmarc_ruf and both
alignment modes. From those comes spoofable, which answers the only question
most buyers are actually asking. It is decided by DMARC, not by SPF: a domain
with a strict -all SPF and no DMARC record can still be spoofed in the From:
header, because SPF checks the envelope. On the Tranco top 300, 79 domains
(26.3 %) publish no DMARC at all and another 31 (10.3 %) publish p=none, which
only monitors.
DKIM selector names cannot be listed from DNS — they are known only to the sender and the receiver — so they must be guessed. The default list of 15 selectors was chosen by measurement: probing 35 selectors across the Tranco top 120 found at least one key for 53 domains (44 %), and these 15 produced almost all of the hits. The full list of 46 is available in the input; it costs roughly three times the queries for a few percent more coverage.
Wildcard zones are detected before the probing starts. Some zones answer
every name under _domainkey with a CNAME. Measured: bing.com returns a
CNAME for zzq7x9-no-such-selector._domainkey.bing.com, and a naive prober
therefore "finds" all 35 selectors there. And this is not one odd domain —
13 of the Tranco top 150 (8.7 %) behave that way in a live run. This Actor
asks for a selector that cannot exist first; if the zone answers, it sets
dkim_wildcard: true and returns no selectors rather than dozens of
invented ones. The run report counts those zones as dkim_wildcard_zones.
MTA-STS is checked in both halves. The DNS record only announces that a
policy exists; the policy applies only if the file at
https://mta-sts.<domain>/.well-known/mta-sts.txt actually loads and says
mode: enforce. A domain with the record and without the file is protected
exactly as much as a domain with neither, and mta_sts_no_policy is a separate
finding for precisely that gap.
TLS-RPT, BIMI and DNSSEC round out the set. DNSSEC looks for a DS
delegation at the parent as well as the resolver's authenticated-data flag,
because the flag alone says the resolver validated something, not that the zone
is signed.
Which TLDs does the registration lookup cover?
All of them that publish anything, and getting there takes two different protocols.
RDAP is the modern replacement for WHOIS and returns structured JSON. Counted
from IANA's own files on 12 September 2026: the root zone holds 1438 TLDs and
1200 of them have an RDAP server, leaving 238 (16.6 %) with none. That
remainder is not obscure — it includes .de, .io, .co, .ch, .it, .eu,
.ru, .jp, .cn, .se, .es and .kz. Verified directly:
rdap.org/domain/example.de and rdap.org/domain/github.io both answer HTTP
404. A tool built on RDAP alone knows nothing about two of the most common
startup TLDs in existence.
Measured on a live cloud run over the Tranco top 150: RDAP answered for 131 domains, WHOIS on port 43 answered for 14 more (9.3 %), and 5 had no registration data anywhere. Those 14 rows are exactly what an RDAP-only tool leaves blank.
So for those zones the Actor falls back to WHOIS on port 43. The responsible
server is discovered from whois.iana.org rather than from a hardcoded table —
measured: .de resolves to whois.denic.de, .io to whois.nic.io, .co to
whois.registry.co — and the answer is parsed into the same fields RDAP
produces. registration_source in every row tells you which path answered.
The text parser handles the formats that break naive parsers. .it returns
Created: twice — once for the domain and once for the registrant, the latter
24 years later — so the first occurrence wins. .eu writes in blocks: the
Registrar: line is empty and the name sits below it as Name:; the parser
tracks which block it is inside, and picks up Name: only under the
registrar, because under the registrant the same key holds a person's name.
What is never collected?
Registrant, administrative, technical and billing contact data — names, email addresses, telephone numbers, postal addresses. Not when RDAP returns it, not when a WHOIS server prints it. The fields this Actor extracts are the registrar (an organisation), the dates, the registry status codes, the transfer lock and the registry nameservers.
What does the TLS certificate check do?
It opens a real TLS handshake on port 443 with verification enabled and
reports issuer, subject, validity window, protocol version, serial and the
number of SANs, plus tls_days_until_expiry.
A certificate that fails verification — expired, self-signed, issued for a
different name — is reported as the finding tls-invalid, not as a missing
certificate. That distinction matters: a silent empty field would tell you the
domain has no certificate when in fact it has a broken one, which is the thing
you were looking for. A domain that simply does not listen on 443 gets
tls_error with the connection failure and no finding, because not serving
HTTPS is not a certificate problem.
The posture score
How is the score calculated?
Every domain starts at 100. Each finding subtracts its published weight and the result is clamped to the 0–100 range. The weights are a convention, not a measurement, so the whole table is printed here — a score whose rule is secret tells you nothing.
| Finding | Weight | Severity |
|---|---|---|
dmarc-missing | 30 | critical |
spf-missing | 25 | high |
spf-all-pass | 25 | critical |
spf-lookup-limit | 20 | critical |
dmarc-policy-none | 20 | high |
tls-invalid | 20 | critical |
spf-multiple-records | 15 | high |
expiry-past | 15 | critical |
spf-all-neutral | 10 | medium |
dkim-none-found | 10 | medium |
expiry-soon | 10 | high |
tls-expiry-soon | 10 | high |
spf-all-softfail | 5 | low |
spf-include-broken | 5 | medium |
dmarc-policy-quarantine | 5 | low |
dmarc-partial-pct | 5 | medium |
dmarc-no-reports | 5 | low |
dnssec-unsigned | 5 | low |
mta-sts-no-policy | 5 | medium |
transfer-unlocked | 5 | medium |
nameserver-mismatch | 5 | medium |
caa-missing | 3 | low |
mta-sts-missing | 3 | low |
mta-sts-testing | 2 | low |
mx-missing | 0 | low |
Grades: A+ from 95, A from 85, B from 70, C from 55, D from
40, F below that. expiry-soon fires inside 30 days, tls-expiry-soon
inside 14.
A finding is only raised for data that was actually collected. Turning off the registration section does not make every domain look transfer-unlocked, and turning off DKIM probing does not accuse every domain of having no key.
Monitor mode
What does monitor mode actually remember?
Twenty fields per domain, stored in a named key-value store so the memory survives between runs: the CNAME chain, MX hosts, nameservers, TXT, CAA, the SPF record and its over-limit flag, the DMARC record and policy, the DKIM selectors found, MTA-STS mode, DNSSEC state, registrar, domain expiry date, registry status, registry nameservers, mail provider, DNS provider, the certificate expiry date and the posture score.
Each returned row carries change_type (NEW, UPDATED, UNCHANGED),
changed_fields listing exactly which of the twenty moved, and
previous_values holding the earlier value for the five short fields worth
keeping in full — DMARC policy, posture score, expiry date, registrar and mail
provider.
Unchanged domains are not written to the dataset, so a daily run over a stable
list costs you nothing in result rows. Set emitUnchanged if you want them
anyway.
Why are A and AAAA records not watched by default?
Because they move on their own. Two cloud runs nine seconds apart reported four
of five domains as changed, and in every case the only difference was the
address set. Checked again with four snapshots ten seconds apart across the ten
prefill domains: three of them returned a different address set — apify.com on
AAAA, stripe.com and vercel.com on A — while MX, NS, TXT and CAA did
not move for a single domain out of the Tranco top 100.
That is load balancing, not a zone edit. Watching it would make anyone
monitoring CDN-backed domains pay for their whole list every single run. If a
change of address is what you are after, watchAddressRecords adds a_records
and aaaa_records to the watch list.
Why do list order changes not count as changes?
Because resolvers shuffle. The order of A records and TXT strings differs
between two consecutive queries — that is load balancing, not a zone edit. Lists
are normalised by sorting before fingerprinting; without that, every domain would
look updated on every run and you would pay for the entire list daily.
The same care applies to new fields: a field that was not in the previous run's memory is not reported as changed, otherwise one update to this Actor would mark every domain in your list as updated and bill you for all of them at once.
Memory holds 20 000 domains. Beyond that the oldest entries are dropped first.
What can monitor mode tell me that a lookup cannot?
An MX change means a mail migration just happened — the best possible moment for a vendor conversation. A weakened DMARC policy means someone turned enforcement off, usually while debugging a campaign, and often forgot to turn it back on. A changed nameserver set with no corresponding registry change is what a hijack looks like. A registrar change on a portfolio you do not own is a competitor consolidating. An approaching expiry date is a renewal you are about to miss.
Filters, and the reason they are careful
Do filters cost me money?
No. Every filter runs after the lookups and before the dataset write, so a domain removed by a filter is never charged.
Can a filter return nothing by accident?
Two protections make that unlikely. First, choosing a finding code that can only
come from a section you switched off turns that section back on for the run and
says so in the log — asking for transfer-unlocked with registration disabled
would otherwise return zero rows from a perfectly green run.
Second, a filter never drops a domain merely because a value is unknown.
expiringWithinDays keeps domains whose expiry date could not be determined —
some zones publish none and some WHOIS servers rate-limit — and counts them
separately in the run report as _kept_expiry_unknown. Dropping them would
quietly assert that they are safe.
When a run does legitimately produce nothing, it says why: the report carries
empty_reason (nothing-changed, filtered-out, source-returned-nothing,
charge-limit-reached) and the log carries a warning naming the number.
Output shape
What do I get per row?
80 fields in the default shape, arranged into five dataset views: Domains (the decision columns), Email security, DNS records, Registration and TLS certificates.
Monitor mode adds three more: change_type, changed_fields and
previous_values.
compactOutput reduces the row to 26 decision fields for AI agents and
spreadsheets. excludeEmptyFields drops nulls and empty lists. Neither changes
which domains are checked or charged — they are shape, not filter.
An empty cell always means the domain does not publish that record. Nothing is inferred, estimated or filled in from a model.
How fast is it and how is the load handled?
Eight domains are processed in parallel; each domain costs roughly 40 DNS queries plus optional WHOIS and TLS connections. Measured on the platform: the built-in ten-domain sample finishes in 4.9 seconds, and 150 domains from the Tranco list in 284 seconds — about 1.9 seconds per domain once WHOIS rate-limits and unreachable HTTPS ports are in the mix. Results are written and the monitor memory is saved every 40 domains, so an interrupted run loses at most one batch — and the order is always deliver first, record second, never the reverse.
Rate limiting is shared across threads by host: when a host asks for a pause, every thread waits, rather than the sleeping one waiting while the rest keep hammering.
Honest limits
- WHOIS servers rate-limit. Measured:
whois.domain-registry.nlstopped answering after a handful of queries in one session. Large batches in the non-RDAP zones will produce some rows withregistration_errorfilled in instead of dates. RDAP zones — 1200 of the 1438 — do not have this problem. - DKIM coverage is a probe, not an enumeration. 44 % of the Tranco top 120
yielded a key to 35 selectors. An empty
dkim_selectors_foundmeans "not among the selectors tried", not "no DKIM". - Many domains do not serve HTTPS on the apex. In the Tranco top 150 run,
36 domains (24 %) could not be connected to on port 443 at all. Those get
tls_errorwith the connection failure and no finding — not serving HTTPS is not a certificate problem. Three domains in the same run had a certificate that failed verification, and those are findings. - No HTTP status or page content. This Actor inspects DNS, registry and the TLS certificate. It does not fetch the website.
- No Standby / instant-API mode. Standby would exclude this Actor from Apify's agentic-payments listing, which is worth more than the convenience.
- IPv6 reverse DNS is not looked up. The nibble form produces a 72-character query name that is almost never populated, and it would cost a query per address.
- Internationalised domains must be supplied in punycode. Raw Unicode names are rejected with a warning rather than silently mangled.
- Certificate Transparency logs are not queried.
crt.shanswered HTTP 502 to a wildcard query during testing and took 14.7 seconds for a single domain; it is not dependable enough for a run that must finish.
FAQ
Do I need an API key for anything?
No. DNS-over-HTTPS at Google and Cloudflare, the IANA RDAP bootstrap, the RDAP
servers themselves, whois.iana.org, the registry WHOIS servers and the MTA-STS
policy files are all public and unauthenticated. Nothing here needs a proxy or a
headless browser either.
What happens if a domain does not exist?
The row still comes back, with resolved: false and dns_status: NXDOMAIN. Use
onlyResolved to drop those. A domain that answers NOERROR with no A record —
common for mail-only domains — counts as resolved if it has MX, NS or TXT.
Can I feed it URLs or email addresses instead of domains?
Yes. https://www.example.co.uk/pricing?ref=1 and sales@example.co.uk are both
accepted; schemes, paths, queries, ports, trailing dots and letter case are
normalised away. Duplicates are removed before anything is charged, and anything
that cannot be read as a domain is reported in the log and counted in the report
rather than silently dropped.
How do I use it as a daily email-security monitor?
Put your domain list in, switch on Monitor mode, and schedule the Actor
daily. The first run returns everything as NEW and fills the memory. Later runs
return only the domains that moved. Combine with minSeverity: high to be told
only about the serious drift.
How do I find spoofable domains in a prospect list?
Switch on onlySpoofable, or filter on findingCodes for dmarc-missing and
dmarc-policy-none. Every returned row already carries the posture score, the
worst severity and the finding list, so the output sorts itself.
How do I watch a domain portfolio for expiry and hijacking?
Use expiringWithinDays for renewals, and monitor mode for the rest: a change in
registry_status, registry_nameservers or registrar is what an unauthorised
transfer looks like. transfer_locked tells you whether the registry lock is
even in place.
Why does one of my domains show a lower score than I expect?
Open findings on that row. Every entry names the code, the severity, the weight
it subtracted and a sentence explaining it. The score is only the sum; the
findings are the substance.
Does the daily run cost the same as the first one?
In monitor mode, no. Only changed domains are written, and only written rows are
billable results. A stable list of a thousand domains that nobody touched returns
zero rows and says empty_reason: nothing-changed.
What is in the run report?
REPORT in the key-value store: domains requested and checked, rows written,
unusable input entries, which source answered for registration, how many zones
answer DKIM probes with a wildcard, how many SPF records are over the RFC limit,
how many TLS certificates failed verification versus were unreachable, the
change counts, and exactly what each filter removed before you were charged.
What else is this called?
Names buyers use: WHOIS bulk lookup, DNS records scraper, DMARC checker, DMARC policy audit, SPF record checker, SPF lookup limit, DKIM selector discovery, MX record lookup, WHOIS expiry alert, domain expiry monitor, RDAP lookup API. All of them read the same record set this Actor returns.
Other names for what this returns: reverse DNS PTR lookup, DMARC RUA aggregate reports, SMTP TLS reporting, DNSSEC validation check, SPF too many DNS lookups, domain expiration date checker, SSL certificate expiry alert and SSL expiry monitor, SaaS vendor detection over DNS, email security posture score, registrar IANA id, MX record health check. Each of those is a field in the row: reverse_dns, dmarc_rua, tls_rpt_record, dnssec_signed, spf_dns_lookups, expires_at, tls_days_until_expiry, verified_services, posture_grade, registrar_iana_id, mx_records.