Domain Expiry & WHOIS/RDAP Change Monitor
Pricing
$2.00 / 1,000 domain checkeds
Domain Expiry & WHOIS/RDAP Change Monitor
Tracks domain expiry, registrar, nameserver, WHOIS/RDAP status and DNS changes for up to 5000 domains per run. Get a changefeed of only what changed since the last check -- expiring soon, registrar moved, went available -- via dataset, webhook, or Apify integrations.
Pricing
$2.00 / 1,000 domain checkeds
Rating
0.0
(0)
Developer
Changefeeds Tools
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Domain Watch — domain expiry, WHOIS/RDAP & DNS change monitor
For domain portfolio owners, registrars, brand-protection teams, and anyone who needs to know the moment a watched domain's expiry, registrar, nameservers, WHOIS/RDAP status, or DNS records move. Feed it up to 5000 domains and a schedule; it returns a changefeed — only what's different since the last run — instead of a full re-scrape you have to diff yourself.
Apify actor that checks a list of domains against RDAP — the official, public, JSON successor to WHOIS — and flags what has changed since the last run: expiries coming up, registrar moves, nameserver changes, DNS changes, and registration-status changes.
What it does
For each domain it:
- Resolves the official RDAP server from the IANA bootstrap file
(
https://data.iana.org/rdap/dns.json), falling back tordap.org. - Fetches registration data: registrar, IANA registrar ID, registration / expiry / last-changed dates, EPP status flags, nameservers, DNSSEC.
- Optionally resolves A, AAAA, MX, NS and TXT records.
- Diffs against the previous run (stored in a key-value store) and emits change flags (see below).
- Writes one dataset row per domain, a summary object to the default
key-value store (key
OUTPUT), and optionally POSTs changed/expiring rows to a webhook. - Requests are polite via one shared per-host limiter: max 1 in-flight
request per host, at least 500 ms between requests per host (at least
1100 ms for
rdap.org, which allows only 10 requests per 10 seconds),429responses honored withRetry-Afteras a host-wide cooldown, manually-followed redirects (max 3 hops), 15 s timeout.
What is compared (and what is not)
The diff compares, per domain: registration status, registrar, expiry date, EPP status flags, nameservers, and each DNS record type (A, AAAA, MX, NS, TXT). Fields that essentially never change — creation date, last-changed date, registrar IANA ID, DNSSEC — are reported in the row but not diffed.
Change flags: baseline (first run), none, became_registered,
became_available (no registration found at the registry — not a guarantee
it can be bought),
registrar_changed, expiry_changed, status_changed,
nameservers_changed, dns_changed:<type>, lookup_failed (RDAP lookup
failure — no field comparison is made), snapshot_read_failed, and
snapshot_write_failed (the result was delivered and charged, but its
history snapshot could not be stored after retries).
A transiently failed DNS record type (e.g. a timeout) does not
produce lookup_failed: the last observed value for that record type is
kept, the comparison is skipped for it, and the other record types are
still compared. A DNS record type that has never been observed before is
baselined silently — its first successful observation produces no
dns_changed:<type> flag (the snapshot distinguishes "never observed"
from "observed empty").
Input
{"domains": ["example.com", "https://www.example.io/about"],"expiringWithinDays": 30,"includeDns": true,"snapshotKey": "my-monitor-set","webhookUrl": "https://example.com/hook"}
domains(required, max 5000): schemes, paths andwww.are stripped automatically; internationalized names are punycoded.expiringWithinDays(default 30): flag domains expiring within this window.includeDns(default true): resolve and diff DNS records. Whenfalse, the previous run's DNS records are kept in the snapshot unchanged (nodns_changedflags are produced and no DNS history is lost). Only observed record types are stored: a type never seen before is treated as unknown and baselined silently on its first observation.snapshotKey(optional): key for the previous-run snapshot. Default is a hash of the sorted domain list, so identical domain sets share a snapshot.webhookUrl(optional): receives{ summary, rows }with changed and expiring rows (max 500) after the run.
Output
One dataset row per domain:
{"domain": "example.com","status": "registered","registrar": "RESERVED-INTERNET ASSIGNED NUMBERS AUTHORITY","registrar_iana_id": "376","created": "1995-08-14T04:00:00Z","expires": "2027-08-13T04:00:00Z","updated": "2026-08-01T18:32:11Z","days_to_expiry": 319,"expiring_soon": false,"rdap_status": "200","status_flags": ["server delete prohibited", "server transfer prohibited", "server update prohibited"],"nameservers": ["a.iana-servers.com", "b.iana-servers.com"],"dnssec": true,"dns": { "a": ["93.184.216.34"], "aaaa": [], "mx": [], "ns": [], "txt": [], "dns_error": [] },"changes": ["baseline"],"checked_at": "2026-09-28T12:00:00.000Z","error": null}
status is one of:
registered— the registry's RDAP server returned the domain object.not_registered— the registry's own RDAP server (found via the IANA bootstrap) answered 404: no registration found at the registry (not a guarantee it can be bought).unsupported_tld— no authoritative RDAP server is known for the TLD (therdap.orgfallback returned 404 or the TLD is missing from the bootstrap). Not charged; the snapshot is not overwritten.error— lookup or infrastructure failure. Not charged; the snapshot is not overwritten.
The summary written to the key-value store under key OUTPUT looks like:
{"domains": 2, "registered": 2, "not_registered": 0, "unsupported_tld": 0,"errors": 0, "expiring_soon": 0, "changed": 2, "first_run": true,"charged": 2, "charge_limit_reached": false,"skipped_after_charge_limit": 0, "snapshot_write_failed": 0,"snapshot_key": "a1b2c3d4e5f60718"}
On a first run every successfully looked-up row gets changes: ["baseline"],
and those rows count toward changed in the summary. Failure rows — status
error, or changes that are only lookup_failed, snapshot_read_failed,
invalid_response or snapshot_write_failed — are excluded from changed
and from the webhook.
Pricing (pay per event)
You pay $0.002 per successfully looked-up domain — that is $2 per 1,000
domains checked (DNS resolution and change detection are included at no
extra charge). Domains whose lookup fails (error, unsupported_tld,
snapshot-read failure) are not charged. Each charged domain is billed
individually; the actor reserves the remaining budget before starting each
domain and stops dispatching new work when your run's max total charge is
reached (recorded in the summary as charge_limit_reached, with
already-charged domains still counted in charged).
What it costs
Pay-per-event, $0.002 per successfully checked domain — nothing else is
billed (no per-run platform fee is configured, DNS resolution and diffing are
included). Domains that fail to look up (error, unsupported_tld, snapshot
read failure) are free.
- First run, 500-domain portfolio: every domain is a fresh baseline, so
all 500 are charged:
500 × $0.002 = $1.00for that run. - Steady state, same 500 domains, daily schedule: each day only the same
500 domains are re-checked (the changefeed just tells you which of them
moved):
500 × $0.002 = $1.00/day →$1.00 × 30 ≈ $30.00/month. - Larger portfolio, 5,000 domains, weekly schedule: per run, once a week →5,000 × $0.002 = $10.00
$10.00 × 4.33 ≈ $43.30/month.
Use cases
Daily expiry and takeover watch for a domain portfolio. Set domains to
your full list (owned domains, competitor names you're tracking, or expired
brand names you want back), add an Apify Schedule (e.g. daily at
06:00 UTC) that reuses the same saved input so every run reads the same
snapshot, and set webhookUrl to a Slack incoming-webhook URL (or a
Zapier/Make "Catch Hook" step) — either directly, or through Apify's
platform-level Integrations tab on the schedule/run, which can also push
rows straight into a Google Sheet. Each morning you get a message with only
the domains that changed: which ones enter the expiringWithinDays window,
which registrar or nameservers moved, and which became available.
Honest limits
- RDAP coverage varies by TLD. All gTLDs (.com, .net, .io, ...) are
covered; some ccTLDs have no RDAP service at all — those fall back to
rdap.org, and when it knows no authoritative server the result isunsupported_tld(not "available"). - "Not registered" is not a purchase guarantee. A 404 from the registry means no registration was found at that registry; premium/reserved names, registry locks, or landrush states can still prevent registration.
- Registrant contact data is usually redacted under GDPR/registry policy, and this actor does not collect it. You get registrar, dates, status, nameservers and DNSSEC only.
- Some registries return sparse data (no expiry date, no registrar ID); those
fields are
null. - Failed RDAP lookups do not overwrite the previous snapshot and are reported
as
lookup_failedinstead of fabricated field changes. A transient DNS failure keeps the last observed value for the affected record type, skips the comparison for it, and still compares every other record type — an outage never erases or falsifies your change history.
Scheduling
In Apify, open the actor and add a schedule (e.g. daily at 06:00 UTC)
with your saved input. Each scheduled run reads the previous run's snapshot,
so you automatically get change flags like expiry_changed and
became_registered every day.