Domain Expiry Monitor: Bulk WHOIS/RDAP & DNS Changes avatar

Domain Expiry Monitor: Bulk WHOIS/RDAP & DNS Changes

Pricing

$2.00 / 1,000 domain checkeds

Go to Apify Store
Domain Expiry Monitor: Bulk WHOIS/RDAP & DNS Changes

Domain Expiry Monitor: Bulk WHOIS/RDAP & DNS Changes

Check up to 5,000 domains per run for expiry date, registrar, nameservers, status and DNS records from official RDAP, flagged with what changed since your last run: expiring soon, registrar moved, became available. $2 per 1,000 domains; failed lookups free.

Pricing

$2.00 / 1,000 domain checkeds

Rating

0.0

(0)

Developer

Changefeeds Tools

Changefeeds Tools

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

  • What you get: one row per domain with expiry date, days to expiry, registrar, nameservers, EPP status flags, DNSSEC and A/AAAA/MX/NS/TXT records, read from the registry's official RDAP server (the JSON successor to WHOIS).
  • Compared with your last run: each row lists what changed: expiry_changed, registrar_changed, nameservers_changed, status_changed, became_available, became_registered, dns_changed:<type>, plus an expiring_soon flag. Up to 5,000 domains per run.
  • Price: $2 per 1,000 domains checked, DNS included. Failed lookups and unsupported TLDs are free.

Sample output

The prefilled list (example.com, apify.com) run on 2026-10-23, compared with our previous run of the same list:

domainregistrarexpiresdays_to_expirynameserverschanges
example.comRESERVED-Internet Assigned Numbers Authority2027-08-13295elliott / hera.ns.cloudflare.comnone
apify.comAmazon Registrar, Inc.2035-06-023,1454 × awsdnsdns_changed:aaaa, dns_changed:txt

The example.com row in full:

{
"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-14T08:01:43Z",
"days_to_expiry": 295,
"expiring_soon": false,
"rdap_status": "200",
"status_flags": ["client delete prohibited", "client transfer prohibited", "client update prohibited"],
"nameservers": ["elliott.ns.cloudflare.com", "hera.ns.cloudflare.com"],
"dnssec": true,
"dns": {
"a": ["104.20.23.154", "172.66.147.243"],
"aaaa": ["2606:4700:10::6814:179a", "2606:4700:10::ac42:93f3"],
"mx": ["0 "],
"ns": ["elliott.ns.cloudflare.com", "hera.ns.cloudflare.com"],
"txt": ["_k2n1y4vw3qtb4skdx9e7dxt97qrmmq9", "v=spf1 -all"],
"dns_error": []
},
"changes": ["none"],
"checked_at": "2026-10-23T03:50:06.018Z",
"error": null
}

mx: ["0 "] is a null MX record (priority 0, no mail host): the domain accepts no email.

Your first run is a baseline. Every domain it reads gets changes: ["baseline"] and its snapshot is saved; change flags start from the second run of the same list. expiring_soon works from the first run.

How to run in 30 seconds

  1. Click Try for free. The input already holds example.com and apify.com.
  2. Click Start. Our first run of that input took 16 seconds and cost $0.004 (2 domains).
  3. Paste your own domains (one per line; https://, paths and www. are stripped), set Expiring within (days), then save it as a task and add a daily schedule.

Pricing

  • $0.002 per domain looked up successfully ($2 per 1,000). DNS resolution and change detection are included.
  • Free: domains whose lookup fails (error), TLDs without an RDAP server (unsupported_tld), and snapshot read failures.
  • Every run checks every domain on the list, so you pay per domain per run:
    • first run, 500 domains: $1.00;
    • the same 500 domains daily: $1.00 a run, about $30 a month;
    • 5,000 domains weekly: $10.00 a run, about $43 a month.
  • Set a maximum total charge and the actor stops starting new domains when it is reached (charge_limit_reached: true in the summary); domains already checked are still counted.

FAQ

How do I get expiry alerts in Slack? Save your list as a task, add a daily schedule, and set Webhook URL to a Slack incoming webhook (or a Zapier/Make "Catch Hook"). After each run it POSTs { summary, rows } with the changed and expiring rows (up to 500). Or attach an Apify integration (Slack, Google Sheets, Zapier, Make) to the task's "run succeeded" event.

Is this WHOIS? It uses RDAP, the registries' official JSON replacement for WHOIS, found through the IANA bootstrap file (https://data.iana.org/rdap/dns.json) with rdap.org as the fallback. You get the same registration facts in a stable format.

Does became_available mean I can buy the domain? No. It means the registry's own RDAP server no longer finds a registration. Premium, reserved or locked names can still be unavailable.

Why does a domain show dns_changed:aaaa on every run? Domains behind a CDN rotate their A/AAAA addresses: apify.com (CloudFront) returned a different set of eight AAAA records 7 minutes apart on 2026-10-23. Ignore those types for CDN-hosted domains, or turn Include DNS records off.

What happens when a lookup fails? The row gets lookup_failed, it is not charged, and the saved snapshot is not overwritten, so a registry outage never looks like a change.

Limits

  • RDAP coverage varies by TLD. All gTLDs (.com, .net, .org, .app, ...) 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 is unsupported_tld (free, never reported as "available").
  • No registrant contacts. Registrant data is usually redacted under GDPR and registry policy, and this actor does not collect it.
  • Some registries return sparse data (no expiry date, no registrar ID); those fields are null.
  • A failed DNS lookup is not a change. A record type whose lookup fails transiently (for example a timeout) keeps the last observed value, is not compared this run, and the other record types are still compared. A DNS record type that was never observed before is baselined silently: its first successful observation produces no dns_changed flag.
  • Not compared: creation date, last-changed date, registrar IANA ID and DNSSEC are reported in the row but never flagged.
  • Polite by design: at most one request in flight per host, at least 500 ms apart (1,100 ms for rdap.org, which allows 10 requests per 10 seconds), HTTP 429 Retry-After honoured, 15-second timeout. 5,000 domains on one registry take a while.

Change flags

baseline (first run), none, became_registered, became_available, registrar_changed, expiry_changed, status_changed (EPP flags), nameservers_changed, dns_changed:<a|aaaa|mx|ns|txt>, lookup_failed (RDAP lookup failure; nothing is compared), snapshot_read_failed, and snapshot_write_failed (the row was delivered and charged, but its history snapshot could not be stored after retries).

Input reference

{
"domains": ["example.com", "https://www.example.io/about"],
"expiringWithinDays": 30,
"includeDns": true,
"snapshotKey": "my-monitor-set",
"webhookUrl": "https://example.com/hook"
}
  • domains (required, max 5,000): schemes, paths and www. are stripped; internationalized names are punycoded.
  • expiringWithinDays (default 30): set expiring_soon on domains expiring within this window.
  • includeDns (default true): resolve and compare DNS records. When false, the previous run's DNS records stay in the snapshot unchanged, so no DNS history is lost.
  • snapshotKey (optional): which saved snapshot to compare against. Default is a hash of the sorted domain list. Set a fixed name to keep history when you edit the list.
  • webhookUrl (optional): receives { summary, rows } with changed and expiring rows (max 500) after the run.

Output reference

status is one of:

  • registered: the registry's RDAP server returned the domain.
  • not_registered: the registry's own RDAP server (found via the IANA bootstrap) answered 404.
  • unsupported_tld: no authoritative RDAP server is known for the TLD. Not charged; the snapshot is not overwritten.
  • error: lookup or infrastructure failure. Not charged; the snapshot is not overwritten.

The summary is saved to the default key-value store as OUTPUT. From the 2026-10-23 run above:

{
"domains": 2, "registered": 2, "not_registered": 0, "unsupported_tld": 0,
"errors": 0, "expiring_soon": 0, "changed": 1, "first_run": false,
"charged": 2, "charge_limit_reached": false,
"skipped_after_charge_limit": 0, "snapshot_write_failed": 0,
"snapshot_key": "0f7391b3d73db6ec"
}

On a first run every successfully looked-up row counts toward changed. 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.