Domain Expiry & WHOIS/RDAP Change Monitor avatar

Domain Expiry & WHOIS/RDAP Change Monitor

Pricing

$2.00 / 1,000 domain checkeds

Go to Apify Store
Domain Expiry & WHOIS/RDAP Change Monitor

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

Changefeeds Tools

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

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:

  1. Resolves the official RDAP server from the IANA bootstrap file (https://data.iana.org/rdap/dns.json), falling back to rdap.org.
  2. Fetches registration data: registrar, IANA registrar ID, registration / expiry / last-changed dates, EPP status flags, nameservers, DNSSEC.
  3. Optionally resolves A, AAAA, MX, NS and TXT records.
  4. Diffs against the previous run (stored in a key-value store) and emits change flags (see below).
  5. 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.
  6. 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), 429 responses honored with Retry-After as 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 and www. are stripped automatically; internationalized names are punycoded.
  • expiringWithinDays (default 30): flag domains expiring within this window.
  • includeDns (default true): resolve and diff DNS records. When false, the previous run's DNS records are kept in the snapshot unchanged (no dns_changed flags 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 (the rdap.org fallback 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.00 for 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:
    5,000 × $0.002 = $10.00
    per run, once a week → $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 is unsupported_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_failed instead 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.