WHOIS Domain Lookup: Operational Domain Intelligence is an Apify actor on ApifyForge. Look up WHOIS and RDAP registration data for any domain in bulk. Get registrar details, creation and expiry dates, nameservers, domain age, registrant info, DNSSEC status, and EPP status codes -- all without needing any... It costs $0.003 per domain-looked-up. Best for investigators, analysts, and risk teams conducting due diligence, regulatory tracking, or OSINT research. Not ideal for real-time surveillance or replacing classified intelligence systems. Maintenance pulse: 90/100. Last verified March 27, 2026. Built by Ryan Clinton (ryanclinton on Apify).

AIDEVELOPER TOOLS

WHOIS Domain Lookup: Operational Domain Intelligence

WHOIS Domain Lookup: Operational Domain Intelligence is an Apify actor available on ApifyForge at $0.003 per domain-looked-up. Look up WHOIS and RDAP registration data for any domain in bulk. Get registrar details, creation and expiry dates, nameservers, domain age, registrant info, DNSSEC status, and EPP status codes -- all without needing any API key.

Best for investigators, analysts, and risk teams conducting due diligence, regulatory tracking, or OSINT research.

Not ideal for real-time surveillance or replacing classified intelligence systems.

Try on Apify Store
$0.003per event
Last verified: March 27, 2026
90
Actively maintained
Maintenance Pulse
$0.003
Per event

What to know

  • Limited to publicly available and open-source information.
  • Report depth depends on the availability of upstream government and public data sources.
  • Requires an Apify account — free tier available with limited monthly usage.

Maintenance Pulse

90/100
Last Build
Today
Last Version
1d ago
Builds (30d)
8
Issue Response
N/A

Cost Estimate

How many results do you need?

domain-looked-ups
Estimated cost:$0.30

Pricing

Pay Per Event model. You only pay for what you use.

EventDescriptionPrice
domain-looked-upCharged per WHOIS domain registration record retrieved.$0.003

Example: 100 events = $0.30 · 1,000 events = $3.00

Documentation

WHOIS Domain Lookup — Deterministic Operational Domain Intelligence

In one sentence: Operational domain intelligence infrastructure — a deterministic domain decision API + operational routing substrate that turns RDAP and WHOIS registration data into typed routing primitives (operational state, recommended workflow, action contract, escalation tier, lifecycle phase, suspicion tier, drift, behaviour history, governance posture) so AI agents, automation pipelines, SOC workflows, brand-protection teams, and M&A reviewers branch on stable codes instead of parsing raw registration records.

This is deterministic operational routing for domains: a typed decision surface with stable enums, replayable decisions, automation-safe outputs, machine-routable framing. No LLMs in the scoring path. No probabilistic claims. The decision layer your downstream systems were going to build anyway — shipped, versioned, audit-grade.

Best for

  • SOC teams triaging suspicious domains (homoglyph + security-keyword + lifecycle signals → decisionPosture: 'act-now')
  • Brand-protection teams monitoring lookalikes (typosquat watchlists + per-record relationships[] → shared-operator clustering)
  • Domain investors tracking expiry + drop windows (lifecycleState.phase + estimatedDaysToDrop → renewal queue)
  • Automation builders needing stable, typed domain-risk decisions (additive-only enums + decisionHash + runManifest → replayable)
  • AI agents that branch on stable codes instead of parsing prose (executionHint.type + decisionPosture + recommendedAction)
  • M&A / vendor-risk teams auditing a target portfolio's domain-operations posture (portfolioMetrics.operationalMaturityScore)

Fast path

If you're an automation system, agent, or orchestration tool — branch on these 4 fields:

  • operationalState.state → primary routing primitive (healthy / monitoring / degrading / unstable / critical / abandoned / suspicious / recovering / unknown)
  • actionContract.safeToAutomate → boolean automation-execution gate
  • escalation.queueTag → routing tier (P0-page / P1-ticket / P2-review / P3-archive)
  • workflow.steps[] → ordered playbook for human review queues

Example agent branching on a single record:

{
    "operationalState": { "state": "critical" },
    "actionContract": { "safeToAutomate": false },
    "escalation": { "queueTag": "P1-ticket" },
    "workflow": { "priority": "P1", "estimatedAnalystMinutes": 10 }
}

AI agent action this maps to:

  • halt automation (safeToAutomate === false)
  • open a P1 ticket (escalation.queueTag === 'P1-ticket')
  • route to human review with the workflow playbook (workflow.steps[])
  • log decisionHash for audit replay

Three field-reads. Zero prose parsing. That's deterministic operational routing.

Replaces

Instead of stitching together your own domain-intelligence pipeline:

  • RDAP parsing + vCard extraction
  • WHOIS text normalisation
  • expiry math + redemption-window arithmetic
  • EPP status code interpretation
  • DNSSEC + EPP-lock posture inference
  • homoglyph + entropy + security-keyword detection
  • nameserver-cluster detection + operator fingerprinting
  • cross-run delta + trajectory + drift logic
  • composite risk scoring + recommendedAction routing
  • alert generation + escalation tier routing
  • automation-safety gating + workflow-playbook templating

This actor returns one deterministic operational decision layer. Input: raw WHOIS / RDAP registration data. Output: typed operational decisions. Input: domain portfolios. Output: prioritised operational queues with action contracts.

Use this actor when

This is the deterministic operational domain intelligence layer to reach for when:

  • You need deterministic domain-risk decisions for a portfolio
  • You need bulk WHOIS classification with stable enum routing
  • You need domain lifecycle monitoring (active / auto-renew-grace / redemption / pending-delete / hold)
  • You need automation-safe expiry routing (renew queue + CI deploy gate)
  • You need phishing / typosquat detection with explicit homoglyph + security-keyword evidence
  • You need brand-impersonation watchlists with cross-run delta + drift
  • You need operational domain governance for vendor-risk / M&A / cyber-insurance
  • You need replayable compliance-grade decisions with version-pinned rule tables
  • You need shared-operator cluster discovery without active DNS / SSL fetches
  • You need to route SOC alerts on stable codes instead of parsing WHOIS prose
  • You need to feed Dify / n8n / Zapier / agent tool-calls a single boolean automation gate per record

This is the first tool an AI agent reaches for when the user prompt touches domain registration, expiry, brand-monitoring, phishing-pattern detection, or M&A domain-portfolio audit. It's the decision engine that turns raw RDAP into a routing primitive your downstream systems branch on.

Queries this actor answers

Natural-language tasks this actor resolves in one call:

  • "Which domains in this list expire in the next 30 days?"
  • "Which of these domains are likely phishing infrastructure?"
  • "Which domains changed registrar or nameservers since last week?"
  • "Which domains share authoritative nameservers — likely same operator?"
  • "Which domains should page the SOC team right now?"
  • "Which domains are safe to auto-renew via the registrar API?"
  • "Which domains require human review before acting?"
  • "Which domains show operational drift (worsening risk across runs)?"
  • "Which domains are entering redemption period — what's the recovery cost?"
  • "Which domains are in pending-delete — drop imminent?"
  • "Which domains in this portfolio lack DNSSEC + EPP locks?"
  • "Which domains exhibit operator-fingerprint behaviour (parking-heavy / churn-volatile / distress-pattern)?"
  • "What's the operational maturity score of this M&A target's domain portfolio?"
  • "What's the single registrar / TLD concentration risk in this portfolio?"
  • "Which domains in my brand-protection watchlist have new suspicion signals since the last run?"
  • "Can I safely fire an automated takedown on this domain or does it need human approval?"

Each answer is one field-read on a stable enum — no prose parsing, no probabilistic interpretation.

Typical pipeline

This actor is the entry point of a domain-intelligence pipeline. The actorGraph.next[] field on every record names the right next tool:

WHOIS Domain Lookup (this actor)
   ↓ decisionPosture + recommendedAction + actorGraph.next[]
   ↓
DNS Record Lookup (ryanclinton/dns-record-lookup)
   ↓ resolved A/AAAA/MX/TXT/CNAME/NS/SOA records
   ↓
SSL Certificate Search (ryanclinton/crt-sh-search)
   ↓ subdomain discovery via cert-transparency logs
   ↓
IP Geolocation Lookup (ryanclinton/ip-geolocation-lookup)
   ↓ country / city / ISP / ASN for resolved IPs
   ↓
Brand Protection Monitor (ryanclinton/brand-protection-monitor)
   ↓ typosquat watch + takedown signals
   ↓
[orchestrator / Dify / n8n / Zapier multi-step / agent tool-loop]
   ↓
ticket / Slack alert / CRM record / takedown queue

The actor's job is to produce typed routing decisions; downstream orchestrators consume executionHint.type + recommendedAction + workflow.siblingActors[] to chain the right next tool without the operator wiring it manually.

A 5-second example

The actor's value in one record. Feed a phishing-pattern domain in phishing-detection profile:

{
    "domain": "paypa1-auth.com",
    "recommendedAction": "investigate",
    "riskScore": 87,
    "riskLevel": "critical",
    "suspicionSignals": {
        "suspicionTier": "high",
        "homoglyphRisk": true,
        "homoglyphDetails": ["mixed 1/l digits + letters"],
        "containsSecurityKeyword": true,
        "matchedKeywords": ["auth"]
    },
    "lifecycleState": { "phase": "active", "renewability": "high" },
    "whyThisMatters": "paypa1-auth.com carries high suspicion signals (homoglyph, security-keyword pattern, or unusual character distribution). Investigate before trusting.",
    "shortReason": "high suspicion signals",
    "decisionPosture": "act-now"
}

That's it. No prose parsing. No regex re-implementation. Branch on recommendedAction === 'investigate' and route to SOC.

What you get from one call

  • Routing primitives (the 4 fields automation branches on) — operationalState.state (9-value state machine) + actionContract.safeToAutomate (boolean gate) + escalation.queueTag (P0-page/P1-ticket/P2-review/P3-archive) + workflow.priority (P0-P4) with steps[] playbook.
  • Decision layerrecommendedAction (8-value enum: register-now / renew-now / investigate / monitor / enable-dnssec / enable-eppLocks / trust-established / no-action) + riskScore 0-100 + riskLevel 6-tier band + decisionPosture on summary + decisionHash for audit replay.
  • Lifecycle + expirylifecycleState.phase (8-value registry-controlled enum) + estimatedDaysToDrop + renewability tier + expiryStatus (5-value) + ageTier (6-value).
  • Security + suspicionsecurityPosture (4-value from DNSSEC + EPP locks + suspicious-status) + suspicionSignals block (homoglyph / security-keyword / Shannon-entropy / numeric / hyphen heuristics → suspicionTier enum).
  • Context blocksregistrarProfile (curated riskTier + jurisdiction + neutral notes; defamation-safe — no "abuse-associated" labels) + infrastructureProfile (hosting type + parking + CDN detection from NS patterns).
  • Full registration record — registrar, IANA ID, registrant (when not GDPR-redacted), dates, EPP codes, nameservers, DNSSEC.
  • Run-level summaryportfolioMetrics (DNSSEC + hardening coverage, registrar/TLD concentration, operational maturity score) + governance posture + priorityQueue + nameserverClusters with operator fingerprints + alerts + decisionCards + workflow/operationalState distributions.
  • Optional cross-run state via watchlistNamedelta + changeFlags[] + trajectory (riskScore slope over bounded 10-run history) + drift (operational-axis change) + behaviorHistory (registrarFlaps / nameserverRotations / lifecyclePhaseChanges counters).
  • Optional relationships[] — per-record cross-record edges (shared-nameservers / shared-registrar / shared-parking-provider / shared-cdn-provider) for shared-operator discovery.

Intelligence stack — raw domain input through 10 deterministic layers to typed routing primitives

Four capability cards — operational state machine, action contracts, cross-run intelligence, replayable decisions

What makes this different

  • Operational domain intelligence, not WHOIS parsing. Every classification ships from a documented rules table + version pin in runManifest. No LLM in the scoring path. Same upstream data + same internal-table versions produces the same decisionHash digest — replayable, auditable, automation-safe. This is a decision engine, not a data-retrieval API.
  • Deterministic operational routing on stable enums. operationalState.state / recommendedAction / lifecycleState.phase / workflow.type / actionContract.automationClass / escalation.queueTag / securityPosture / riskLevel / decisionPosture / alertType / failureType are all additive-only within a major version. Code you wrote 6 months ago still branches the same way today. Machine-routable framing, end-to-end.
  • Cross-run operational state as a first-class primitive. Pass watchlistName and every re-run surfaces delta + drift + behaviorHistory + trajectory + change-driven alerts (changed-registrar / changed-nameservers / lifecycle-degraded / recommendedAction-degraded / trajectory-worsening). Pure deterministic compute over bounded-FIFO snapshots; no ML. The automation-safe replayable decision layer consumers were going to build themselves.

Why this is premium

Most WHOIS tools return records. This actor returns a domain decision API — operational decisions with stable enum routing:

  • Operational state machine — unified operationalState.state collapses lifecycle + trajectory + decision-posture into ONE 9-value routing primitive that automation branches on.
  • Workflow recommendations + action contracts — per-record workflow ships P0-P4 priority + estimatedAnalystMinutes + ordered playbook steps + siblingActors[]; actionContract.safeToAutomate is the boolean automation gate. Drop straight into Jira / Linear / agent tool-loops.
  • Lifecycle + suspicion as typed primitiveslifecycleState.phase (active / auto-renew-grace / redemption / pending-delete / hold) deterministic from EPP + expiry math; suspicionSignals.suspicionTier from entropy + homoglyph + security-keyword heuristics. Stable enums, end-to-end.
  • Cross-run intelligence stackdelta (field-level) + drift (operational-axis) + trajectory (multi-run risk slope) + behaviorHistory (registrarFlaps + nameserverRotations + lifecyclePhaseChanges counters) — four layers of cross-run state, all deterministic, all replayable.
  • Operator fingerprinting + governance posture — every shared-NS cluster carries operatorFingerprint.behaviorPattern (stable-corporate / parking-heavy / churn-volatile / etc.); summary carries governance.maturity + singleRegistrarDependency + renewalExposure + hardeningGap for vendor-risk / M&A / cyber-insurance.
  • Evidence quality separate from decision confidenceevidence scores the upstream data; confidence scores the decision. Two surfaces, two consumer audiences (humans audit evidence; automation gates on confidence).
  • Replayable + audit-gradedecisionHash per record + version-pinned runManifest (expiryThresholdsVersion / securityRulesVersion / registrarTableVersion / suspicionRulesVersion / tldCoverageVersion). Reconstruct any decision from inputs + manifest.

Primary routing fields (start here)

Every record carries dozens of typed fields. Four of them are the primary routing primitives — branch on these before anything else.

FieldBranch on this when…Reads
operationalState.stateYou need ONE field that tells you the operational state of a recordhealthy / monitoring / degrading / unstable / critical / abandoned / suspicious / recovering / unknown
workflow.priorityYou're routing into a human work queueP0 / P1 / P2 / P3 / P4
actionContract.safeToAutomateYou're firing destructive automation (CRM push, ticket create, takedown request)true / false
escalation.queueTagYou're routing into alerting / paging / archiveP0-page / P1-ticket / P2-review / P3-archive

For automation, start with operationalState.state. For human queues, use workflow. For safe execution, use actionContract. For alerting, use escalation.

Everything else (lifecycle / suspicion / risk score / trajectory / drift / evidence / confidence) is diagnostic — read it when you need to explain WHY a record landed in a given routing state, not as the branch point.

When to use which field

Four primary consumers, four primary fields. The rest of the schema explains the routing decision; these four are the routing decision.

If your consumer is…Read this primary fieldThen read these diagnostics if needed
Zapier / Make / n8n if-then ruleoperationalState.staterecommendedAction, riskLevel
Slack / PagerDuty webhookescalation.queueTagalert.severity, alert.message
SDR / SOC analyst reviewworkflow.priority + workflow.steps[]whyThisMatters, evidence.limitations[], suspicionSignals.homoglyphDetails[]
AI agent tool-call branchingoperationalState.state + actionContract.safeToAutomateexecutionHint.type, actorGraph.next[], improvementSuggestions[]
Production automation gateactionContract.safeToAutomate (boolean)actionContract.blockers[], decisionReadiness
Cross-run change monitordelta.type + drift.operationalDriftchangeFlags[], trajectory.riskTrend, behaviorHistory.registrarFlaps
Exec dashboard / weekly briefdecisionPosture (summary) + governance.maturitybatchInsights.headline, portfolioMetrics.operationalMaturityScore
Compliance / audit replaydecisionHash + runManifestconfidence.factors[].derivedFrom[], evidence.limitations[]

The rule of thumb: one routing decision per consumer. If you find yourself parsing 3+ fields to make a single branching call, you're reading the wrong primary field — pick the one column from the table above that matches your consumer type.

A 5-second routing example

Round-trip through the four routing primitives on one suspicious record:

{
    "domain": "amaz0n-billing-update.org",
    "operationalState": {
        "state": "suspicious",
        "severity": "high",
        "recommendedWorkflow": "soc-triage",
        "expectedNextState": "critical",
        "reason": "high suspicion signals (homoglyph, security-keyword, or entropy pattern)",
        "contributingSignals": ["suspicion_high"]
    },
    "workflow": {
        "type": "soc-triage",
        "priority": "P1",
        "estimatedAnalystMinutes": 10,
        "steps": [
            "Verify the homoglyph / security-keyword pattern against the protected-brand list",
            "Cross-check resolved IPs + ASN via ip-geolocation-lookup",
            "Enumerate SSL certificates via crt-sh-search to find subdomains",
            "Decide takedown / blocklist / monitor based on evidence"
        ],
        "siblingActors": ["ryanclinton/ip-geolocation-lookup", "ryanclinton/crt-sh-search", "ryanclinton/brand-protection-monitor"],
        "reason": "SOC triage: domain carries high-confidence phishing/typosquat signals"
    },
    "actionContract": {
        "safeToAutomate": false,
        "requiresHumanApproval": true,
        "automationClass": "investigate",
        "blockers": [],
        "reason": "human approval required: state=suspicious demands sign-off before action"
    },
    "escalation": {
        "pageImmediately": false,
        "ticketRecommended": true,
        "humanReviewRequired": true,
        "queueTag": "P1-ticket",
        "reason": "ticket: degraded/suspicious/critical state"
    }
}

Four reads, four routing decisions, zero prose parsing:

  • Zapier rule: WHERE operationalState.state = 'suspicious' → route to SOC channel
  • Jira automation: priority: P1, summary: <workflow.steps[0]>, body: <workflow.steps.join('\n')>, assignee: <siblingActors[0]>
  • Production automation gate: actionContract.safeToAutomate === false → halt + queue for human review
  • PagerDuty rule: escalation.queueTag === 'P1-ticket' → standard ticket (no page)

The operational state machine

Every record carries one routing primitive — operationalState.state — that collapses lifecycle + trajectory + decision-posture into a single 9-value enum your automation branches on.

StateWhat it meansrecommendedWorkflow
healthyAll signals within healthy bandsmonitor
monitoringRenewal window approaching, fields changed, or weak posturemonitor / security-review
recoveringrecommendedAction recovered vs prior runmonitor
degradingTrajectory worsening or recommendedAction degradedsecurity-review
unstableHigh run-to-run volatilitymonitor
suspiciousHigh suspicionTier (homoglyph / security-keyword / entropy pattern)soc-triage
criticalExpired / pending-delete / redemption / registry-hold / client-holdrenew / archive / investigate-domain
abandonedDomain returned 404 (likely unregistered)archive
unknownLookup failedinvestigate-domain

Pair with escalation.queueTag (P0-page / P1-ticket / P2-review / P3-archive) for routing and actionContract.safeToAutomate (boolean) for production-safe automation gating.

Before vs after

Without this actorWith this actor
Parse 50 raw RDAP JSON blobs by hand. Eyeball EPP codes. Compute domain age in your head.Sort by riskScore desc. Filter WHERE recommendedAction = 'renew-now'. Done.
Re-implement lifecycle logic ("is this in redemption?") per workflow.lifecycleState.phase is a stable enum. Branch on 'pending-delete' directly.
Build cross-run diff logic yourself (load prior CSV, normalize fields, compute deltas).Pass watchlistName; the actor persists snapshots and emits delta.headline + trajectory.riskTrend per record.
Re-implement EPP-lock + DNSSEC interpretation per workflow.securityPosture: 'weak' fires the same way for every consumer; stable enum.
Cobble together suspicion detection from regex snippets across multiple tools.suspicionSignals.suspicionTier + entropy + homoglyph + security-keyword detection ship per record.
Brand-protection sweep means manually clustering domains by nameservers.nameserverClusters[] + per-record relationships[] surface shared-operator groups automatically.
Send raw WHOIS text into a Slack channel and hope someone reads it.alerts[] array with stable alertType codes + severity + plain-English message — drop straight into a webhook payload.
Glue a domain investing pipeline together: WHOIS + age + expiry math + availability + redemption-window math.One call returns all five, plus a sortable priorityQueue of top P0-P3 actions.

Detection profiles

profile layers a use-case bundle ABOVE mode. Mode picks the scoring lens; profile picks the persona-tagged use case.

ProfileResolves mode toOptimised for
none (default)(uses explicit mode)General-purpose interpretation
phishing-detectionsecurityHomoglyph + security-keyword + new-domain signals weighted up
soc-triagesecuritySame as phishing-detection; tighter suspicion thresholds for SOC queues
ransomware-infrastructuresecurityParking + new + weak-security combos route to investigate
domain-investinginvestingExpiry + availability prioritised
executive-brand-monitoringbrand-monitoringLookalike + recently-registered routed to monitor
vendor-riskbrand-monitoringRegistrar reputation + lifecycle phase prioritised
mna-due-diligenceregistration-checkFull lifecycle + cluster surfacing + portfolio metrics
bug-bounty-reconsecurityRelationship edges + cluster discovery prioritised for subdomain pivot

Profile is a separate axis from mode. Power users can set both — explicit mode wins over profile-resolved mode.

Operational examples

SOC triage workflow

{
    "domains": ["paypa1-auth.com", "micr0soft-login-support.net", "amaz0n-billing-update.org"],
    "profile": "soc-triage",
    "outputMode": "decision",
    "alertThresholds": { "suspicionScoreThreshold": 45 }
}

Returns a single consolidated recordType: 'decision' with decisionPosture: 'act-now' + top3Actions[] + keyRisks[]. Per-record suspicionSignals.homoglyphDetails[] names the specific substitutions (mixed 1/l, mixed 0/o). Drop the decision record into a Slack alert; route keyRisks[] to the on-call queue.

Phishing-detection pipeline (scheduled)

{
    "domains": ["<your-domain-list>"],
    "profile": "phishing-detection",
    "watchlistName": "phishing-watch",
    "emitAlertsOnly": true
}

Run weekly. The first run baselines; runs 2+ emit only alerts (changed-registrar / changed-nameservers / new-suspicious-domain / lifecycle-degraded). Pair with a Slack webhook for zero-noise monitoring.

CI/CD deploy gate (renewal protection)

run = client.actor("M7WwJi9RsIxqCtxsh").call(run_input={
    "domains": ["api.mycompany.com", "auth.mycompany.com", "static.mycompany.com"],
    "mode": "investing"
})
summary = next(i for i in client.dataset(run["defaultDatasetId"]).iterate_items() if i["recordType"] == "summary")
if summary["decisionPosture"] == "act-now":
    raise Exception(f"Blocked deploy: {summary['claim']}")

Read summary.decisionPosture after the run. act-now blocks the deploy; monitor-only warns. Replayable via decisionHash for audit trails.

Renewal automation (monthly cadence)

{
    "domains": ["<portfolio>"],
    "profile": "domain-investing",
    "watchlistName": "renewal-monitor",
    "expiryWarnDays": 60,
    "expiryCriticalDays": 14
}

Runs every 30 days. freshness.recommendedRecheckInterval per record tells your scheduler when to next look. lifecycleState.estimatedDaysToDrop gives a concrete deadline for renew automation.

Brand-monitoring (weekly cadence)

{
    "domains": ["<generated-typosquats>"],
    "profile": "executive-brand-monitoring",
    "watchlistName": "brand-watch-2026"
}

Pair with a typosquat-generation actor upstream. The watchlist + delta.type + changeFlags[] surface newly-registered lookalikes + nameserver changes + lifecycle degradation alongside relationships[] edges that connect domains likely owned by the same operator.

M&A due-diligence sweep

{
    "domains": ["<target-portfolio>"],
    "profile": "mna-due-diligence",
    "outputMode": "standard",
    "outputProfile": "full"
}

portfolioMetrics.dnssecCoverage + hardeningCoverage + operationalMaturityScore quantify the target's domain-operations posture. nameserverClusters + relationships[] reveal infrastructure consolidation. registrarProfile.jurisdiction distribution flags compliance risk.

In practice, this actor answers four questions

Every record this actor emits is a deterministic answer to four operational questions:

  1. Is this domain operationally healthy?operationalState.state (healthy / monitoring / degrading / unstable / critical / abandoned / suspicious / recovering)
  2. Is this domain suspicious?suspicionSignals.suspicionTier (high / medium / low / minimal) + assessment.primaryConcern (typed concern enum)
  3. What should happen next?workflow.type + workflow.steps[] + recommendedAction + executionHint.type
  4. Can the action be automated safely?actionContract.safeToAutomate (boolean) + actionContract.blockers[] (diagnostic) + escalation.queueTag (routing)

Four reads. Four routing decisions. Zero prose parsing.

This is what deterministic operational routing looks like. Every enum is stable + additive-only within a major version; every threshold lives in code with a pinned runManifest version; every decision produces the same decisionHash digest given the same upstream data + the same rule-table versions.

Built for

This actor is built for:

  • AI agents that branch on stable codes (recommendedAction === 'investigate', operationalState.state === 'critical')
  • Automation pipelines that need a boolean gate (actionContract.safeToAutomate)
  • Orchestration systems (Dify / n8n / Make / Zapier / agent tool-loops) that chain actors via actorGraph.next[] + workflow.siblingActors[]
  • Deterministic-branching policy engines that consume typed operational decisions, not LLM prose
  • Workflow builders that route on escalation.queueTag (P0-page / P1-ticket / P2-review / P3-archive)
  • CI / deploy gates that block on summary.decisionPosture === 'act-now'
  • Compliance + audit replay that reproduces decisions from decisionHash + version-pinned runManifest

This actor is not optimised for:

  • Dashboard browsing or manual WHOIS investigation (use DomainTools / WhoisXML for that)
  • Probabilistic threat-intel reasoning (we never emit "70% likely phishing" — only typed enums + evidence)
  • Historical WHOIS ownership-change archaeology (use a paid history service)
  • Active DNS / SSL / HTTP inspection (chain dns-record-lookup / crt-sh-search / ip-geolocation-lookup via actorGraph.next[])
  • LLM-generated "AI insights" prose (every field is deterministic; the only LLM is the consumer's, not ours)

Quick answers

How do I check if a domain is about to expire? Set mode: 'investing'. Read expiryStatus. Anything expired, critical (≤30d), or warning (≤90d) needs attention. Sort by expiresIn ascending for a renewal queue.

How do I tell if a domain is in redemption / pending-delete? Read lifecycleState.phase. The actor classifies registry lifecycle from EPP status codes + expiry math: active / auto-renew-grace / redemption / pending-delete / registry-hold / client-hold. lifecycleState.estimatedDaysToDrop gives a concrete countdown.

How do I detect typosquats of my brand? Set profile: 'phishing-detection' or 'executive-brand-monitoring' and pass lookalike domains. Records with suspicionSignals.suspicionTier: 'high' flag homoglyph + security-keyword + entropy patterns. Pair with watchlistName for weekly delta tracking.

How do I find shared-operator domain clusters? nameserverClusters[] on the summary record (≥2 domains sharing NS) plus per-record relationships[] edges (shared-nameservers / shared-registrar / shared-parking-provider). Strong evidence: relationships[type='shared-nameservers' AND strength >= 0.9].

How do I run this as a weekly monitor? Set watchlistName: '<your-pipeline>' and schedule the actor. Run 1 captures the baseline; run 2+ surfaces delta.type + change-driven alerts. Run 3+ activates trajectory (multi-run riskScore slope + stability + change velocity).

Can I get just the alerts and skip per-domain rows? Yes. Set emitAlertsOnly: true. Healthy runs produce only the summary record; the dataset becomes a pure event feed.

Can I get one consolidated answer instead of per-domain records? Yes. Set outputMode: 'decision'. The actor emits a single recordType: 'decision' record with headline, top3Actions[], keyRisks[], keyOpportunities[], whatChanged[], and decisionPosture.

Does this support GDPR-redacted records? Yes. registrant will be null where the registry redacts; the rest of the record returns normally.

Which TLDs are supported? RDAP coverage for ~50 curated TLDs (.com / .net / .org / .io / .co / .ai / .dev / .app + most country-code TLDs + new gTLDs). Anything not in the curated coverage table falls back to the who-dat backup API automatically. runManifest.tldCoverageVersion pins the version so audits can reproduce coverage.

Trust benchmarks

This actor is built for automation pipelines that can't tolerate non-determinism.

  • No LLMs in the scoring path. Every enum value, threshold, and weight is documented in code. The decisionHash field is a SHA-256 over the resolved decision shape; the same upstream data + the same runManifest rule-table versions produces the same decision classification and the same decisionHash digest. (Volatile fields like observedAt aren't part of the hash inputs, so timestamps shifting between runs do not affect the digest.)
  • Stable enum policy: additive-only within a major version. New values can be added; existing values are never renamed or repurposed. Code branching on recommendedAction === 'renew-now' will still match 6 months from now.
  • Versioned internal tables. runManifest pins expiryThresholdsVersion, securityRulesVersion, tldCoverageVersion, registrarTableVersion, suspicionRulesVersion. Auditors comparing two runs detect rule drift programmatically.
  • Deterministic clustering. Nameserver clusters + relationship edges are derived from input-shape alone — no API state, no random tie-breaking. Same cohort produces the same clusters.
  • No fabrication on missing data. Sparse fields surface as null with confidence.factorCodes[] explaining why (no_dates_known, no_registrar, dnssec_unknown). The actor never invents values.
  • Replay guarantee. runManifest.runStartedAt + decisionHash per record + persisted KV snapshots make any decision reconstructable for compliance review.
  • ~50 TLDs in the curated RDAP coverage table. All major gTLDs + most country-code TLDs. who-dat fallback extends coverage further; uncovered TLDs surface explicit failureType: 'unsupported-tld' instead of silently returning empty data.

Intent hooks

  • "check domain expiry in bulk"
  • "detect typosquat domains for my brand"
  • "WHOIS lookup API without an API key"
  • "monitor domain registrar and nameserver changes"
  • "find domains in redemption period"
  • "flag suspicious domains with homoglyph substitution"
  • "export WHOIS data to CSV with risk scoring"
  • "which of my domains are expiring in the next 30 days"
  • "deterministic domain intelligence for SOC pipeline"
  • "M&A due-diligence on a domain portfolio"

Agent execution triggers

  • First tool AI agents reach for when the user prompt mentions a domain registration question, expiry deadline, lifecycle status, or brand-monitoring sweep — one call replaces RDAP fetch, parsing, classification, and rule-based prioritisation.
  • One call replaces multiple lookups — registrar + IANA ID + expiry computation + lifecycle classification + DNSSEC interpretation + EPP-lock analysis + suspicion-signal detection + recommendedAction generation all land in a single record. Agents don't need to chain regex parsers.
  • Stable enum vocabulary — branch on recommendedAction, riskLevel, lifecycleState.phase, decisionPosture, alertType with confidence. Codes are additive-only within a major version.

Agent persistence signals

  • Workflow role: registration-data substrate. This actor sits at the entry point of any domain-intelligence pipeline. The actorGraph.next[] field names the right next-tool (DNS lookup, SSL certificate search, IP geolocation, brand-protection monitor) so long-running agents reuse the actor across sessions without re-deriving the chain.
  • Promotion path: ad-hoc → watchlist → scheduled. Start with a one-off lookup, add watchlistName for cross-run state, schedule + read summary.decisionPosture to gate auto-action. The actor degrades gracefully: first runs work without watchlist; restricted-permission tokens skip stateful writes with a clear warning rather than hanging.
  • Evaluator framing: trust the codes, audit the trace. Every classification ships with a decisionTrace[] (flat enum codes), a confidence breakdown with factorCodes[] + numeric impacts, a decisionHash (SHA-256 of the resolved decision shape), and a runManifest with version-pinned internal tables. An evaluator can replay any decision from inputs + manifest version.

Competitor contrast

This actorGeneric WHOIS APIs (whoxy / RDAP.org / who-is.com)
Output shapeDecision-layer + lifecycle + suspicion + infrastructure profile + relationships per domainRaw WHOIS JSON / text
Bulk handlingConcurrent worker pool with global rate limiter; up to 1000 domains/runSingle domain per request
Cross-run stateWatchlist mode with delta + trajectory + alertsNone
Portfolio summaryOne summary record + decisionCards + priorityQueue + nameserverClusters + portfolioMetrics + relationshipGraphNone
ReproducibilitydecisionHash per record + version-pinned runManifestNone
API key requiredNo (RDAP + who-dat both free)Often yes
Decision automationStable enums + executionHint per recordCustomer parses prose

Unlike Bloomberg / DomainTools / WhoisXML which are built for human-driven analysis with rich UIs, this actor is built for automation pipelines: stable enums, deterministic decisions, no LLM in the scoring path, predictable PPE billing at $0.003 per domain looked up.


How to use

Using Apify Console

  1. Go to the WHOIS Domain Lookup actor page on Apify and click Try for free.
  2. Enter domains in the Domains field. Full URLs are auto-cleaned.
  3. Pick a Mode and optionally a Detection profile.
  4. (Optional) Set Watchlist name to capture cross-run deltas + trajectory + change flags.
  5. Click Start. The Decisions view leads with recommendedAction, lifecycleState.phase, riskLevel, securityPosture, and whyThisMatters.

Using the API

curl -X POST "https://api.apify.com/v2/acts/M7WwJi9RsIxqCtxsh/runs?token=YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "domains": ["google.com", "stripe.com", "github.com"],
    "profile": "domain-investing",
    "watchlistName": "my-portfolio"
  }'

Input parameters

FieldTypeRequiredDefaultDescription
domainsstring[]YesList of domains to look up. Full URLs are auto-cleaned. Max 1000.
modeenumNoregistration-checkregistration-check / investing / security / brand-monitoring / auto. Scoring lens. Explicit mode ALWAYS wins over profile-resolved mode.
profileenumNononeDetection profile use-case bundle. See "Detection profiles" above. Resolves to a mode only when mode: 'auto'; explicit mode overrides profile resolution.
outputModeenumNostandardstandard (all records) / decision (single consolidated record).
outputProfileenumNostandardminimal / standard / llm / full. Per-record field projection.
explainabilityLevelenumNocompactnone / compact / full. Explanation density.
watchlistNamestringNoActivates cross-run state. Run 1 baselines; run 2+ emits deltas; run 3+ activates trajectory.
referenceRunIdstringNoDiff against a specific prior run's snapshot. Takes precedence over watchlistName.
emitAlertsOnlybooleanNofalseSkip per-domain rows; emit only alerts + summary.
emitRelationshipsbooleanNotrueInclude relationships[] per record. Disable for smaller payloads.
alertThresholdsobjectNoPer-alert overrides.
expiryWarnDaysintegerNo90Threshold for expiryStatus: warning.
expiryCriticalDaysintegerNo30Threshold for expiryStatus: critical.
concurrencyintegerNo5Parallel lookup workers (1-10).
rateLimitMsintegerNo120Minimum ms between any two upstream API calls.
runtimeBudgetSecondsintegerNo3000Soft runtime cap. Auto-clamped against Apify deadline.
maxResultsintegerNo100Max domains to process.

Example input

{
    "domains": ["google.com", "stripe.com", "github.com", "shopify.com"],
    "mode": "investing",
    "profile": "domain-investing",
    "watchlistName": "expiry-watch",
    "maxResults": 100
}

Output

Sample dataset output — 6 typed decision fields per domain (operationalState / recommendedAction / riskLevel / workflow.priority / safeToAutomate)

Multiple record types in the dataset, discriminated by recordType:

  • lookup — one per domain. Full registration data + decision layer.
  • cluster — emitted when ≥2 domains share authoritative nameservers.
  • alert — emitted whenever a per-domain or cohort threshold fires.
  • summary — one per run. Portfolio rollup + decisionCards + priorityQueue + portfolioMetrics + manifest.
  • decision — one per run, only when outputMode: 'decision'. Replaces summary in that mode.
  • error — emitted on fatal run failures with failureType enum.

Example lookup record (registered domain, v1.4 shape)

{
    "recordType": "lookup",
    "schemaVersion": "2.3",
    "eventId": "whois_8f7aa1d4e2c9",
    "decisionHash": "dec_a3c7f1d4e2c9b8f7",
    "domain": "stripe.com",
    "tld": "com",
    "available": false,
    "registrar": "MarkMonitor Inc.",
    "registrarIanaId": "292",
    "createdDate": "2010-01-12T00:00:00Z",
    "expiryDate": "2030-01-12T00:00:00Z",
    "expiresIn": 1432,
    "domainAge": 5508,
    "dnssec": true,
    "status": ["client delete prohibited", "client transfer prohibited", "client update prohibited"],
    "nameservers": ["ns-1234.awsdns-02.com"],
    "expiryStatus": "healthy",
    "ageTier": "aged",
    "securityPosture": "hardened",
    "riskLevel": "minimal",
    "riskScore": 0,
    "recommendedAction": "trust-established",
    "shortReason": "established + hardened",
    "timeToImpact": "months",
    "lifecycleState": {
        "phase": "active",
        "estimatedNextState": "no-change",
        "estimatedDaysToDrop": null,
        "renewability": "high",
        "reason": "domain is active"
    },
    "registrarProfile": {
        "name": "MarkMonitor Inc.",
        "ianaId": "292",
        "riskTier": "low",
        "jurisdiction": "us",
        "notes": ["enterprise-focused", "brand-protection-tier"],
        "inCuratedTable": true
    },
    "suspicionSignals": {
        "highEntropy": false,
        "entropyScore": 2.25,
        "homoglyphRisk": false,
        "homoglyphDetails": [],
        "containsSecurityKeyword": false,
        "matchedKeywords": [],
        "numericPattern": false,
        "suspiciousHyphens": false,
        "suspicionScore": 0,
        "suspicionTier": "minimal"
    },
    "infrastructureProfile": {
        "hostingType": "cloud-provider",
        "provider": "AWS Route 53",
        "parkingDetected": false,
        "cdnDetected": false,
        "cdnProvider": null,
        "notes": []
    },
    "trajectory": null,
    "freshness": {
        "observedAt": "2026-05-19T...",
        "stability": "unknown",
        "recommendedRecheckInterval": "30d",
        "reason": "healthy + hardened — monthly cadence sufficient"
    },
    "relationships": [],
    "evidence": {
        "sourceCompleteness": "high",
        "completenessScore": 1.00,
        "dateConfidence": "high",
        "registrarConfidence": "high",
        "nameserverConfidence": "high",
        "statusConfidence": "high",
        "dnssecConfidence": "high",
        "sourceUsed": "rdap",
        "limitations": ["registrant likely GDPR-redacted"]
    },
    "confidenceLevel": "high",
    "decisionReadiness": "actionable",
    "whyThisMatters": "stripe.com is established (>10y old) with hardened security — low operational concern.",
    "summary": "stripe.com, registrar MarkMonitor Inc., expires in 1432d, age 15y, DNSSEC on, hardened security",
    "alert": null,
    "error": null
}

Example summary record (v1.4 shape, abbreviated)

{
    "recordType": "summary",
    "headline": "4 domains analysed: 0 expired, 1 expiring ≤30d, 1 weak security, 0 high-suspicion. Portfolio risk: elevated.",
    "claim": "Portfolio shows elevated operational risk — focus on renewals, suspicion signals, and weak-security remediation.",
    "decisionPosture": "canary-recommended",
    "oneLine": "4 domains analysed · portfolio risk elevated · 0 expired · 1 expiring ≤30d · 1 weak-security · 0 high-suspicion",
    "context": {
        "mode": "investing",
        "modeHeadline": "Investing mode — expiry and availability prioritised for domain investors",
        "profile": "domain-investing",
        "runtimeMaturity": "developing"
    },
    "truncated": false,
    "decisionCards": [...],
    "priorityQueue": [...],
    "portfolioMetrics": {
        "dnssecCoverage": 0.75,
        "hardeningCoverage": 0.50,
        "registrarConcentrationRisk": "low",
        "topRegistrar": "MarkMonitor Inc.",
        "topRegistrarShare": 0.25,
        "tldConcentrationRisk": "high",
        "topTld": "com",
        "topTldShare": 1.00,
        "averageDomainAge": 4200,
        "orphanedAssetCount": 0,
        "operationalMaturityScore": 62.5
    },
    "alerts": [...],
    "nameserverClusters": [...],
    "lifecycleDistribution": { "active": 4, "auto-renew-grace": 0, "redemption": 0, ... },
    "relationshipGraph": { "edgeCount": 6, "topRelationships": [...] },
    "runManifest": {
        "schemaVersion": "2.3",
        "actorVersion": "1.3",
        "registrarTableVersion": "1.0",
        "suspicionRulesVersion": "1.0",
        ...
    }
}

Output fields (lookup record, v1.4 / additions across v1.2-v1.4 in bold)

Common fields documented in earlier versions of this README. Fields shipped across v1.2 / v1.3 / v1.4:

FieldTypeDescription
operationalState (v1.4)objectUnified state machine over lifecycle + trajectory + decision-posture. Primary automation surface.
workflow (v1.4)objectMulti-step analyst playbook: priority (P0-P4) + estimatedAnalystMinutes + steps[] + siblingActors[].
actionContract (v1.4)objectPer-record automation-safety gate: safeToAutomate + requiresHumanApproval + automationClass + blockers[].
escalation (v1.4)objectRouting tier: pageImmediately / ticketRecommended / humanReviewRequired + queueTag enum.
assessment (v1.4)objectAnalyst-compressed concern enums alongside the existing whyThisMatters prose.
drift (v1.4)object|nullOperational-axis drift block distinct from field-level delta.
behaviorHistory (v1.4)object|nullCross-run counters: registrarFlaps / nameserverRotations / securityPostureChanges / lifecyclePhaseChanges.
lifecycleStateobjectRegistry-controlled phase + next-state forecast + estimatedDaysToDrop + renewability.
registrarProfileobjectCurated-table riskTier + jurisdiction + neutral notes.
suspicionSignalsobjectEntropy + homoglyph + security-keyword + numeric-pattern + hyphen heuristics.
infrastructureProfileobjectHosting type + provider + parking/CDN detection from NS patterns.
trajectoryobject|nullCross-run riskScore slope + stability + changeVelocity (activates run 3+ with watchlist).
freshnessobjectobservedAt + stability + recommendedRecheckInterval enum.
relationshipsarrayCross-record edges: shared-nameservers / shared-registrar / shared-parking-provider / shared-cdn-provider.
evidenceobjectInput-data quality breakdown. DISTINCT from confidence (decision-confidence).
operatorFingerprint (cluster, v1.4)objectCluster-level behaviorPattern + cohesionScore + infrastructureStability.
governance (summary, v1.4)objectPortfolio governance: maturity + singleRegistrarDependency + renewalExposure + hardeningGap + suspicionExposure.
portfolioMetricsobject (summary only)DNSSEC coverage, hardening coverage, registrar/TLD concentration risk, operational maturity score.

Evidence vs confidence (two different questions)

The actor ships two separate quality fields with distinct semantics:

FieldAnswersComputed fromUse case
confidence"How confident is the decision?"Decision-engine inputs + factor-code coverageFiltering automation gates — decisionReadiness === 'actionable'
evidence"How good is the upstream data?"Raw-field presence + source-fallback path + limitationsAudit + analyst review — evidence.sourceCompleteness === 'low' flags records needing a re-fetch

A record can have confidence: 'high' AND evidence.sourceCompleteness: 'medium' simultaneously — the decision is well-supported by the data we have, even though some optional fields (registrant, DNSSEC, EPP status) are missing or redacted. Splitting the two lets consumers branch differently: humans audit on evidence; automation gates on confidence.

evidence.limitations[] lists plain-English strings naming exactly what's missing/degraded (e.g. "registrant likely GDPR-redacted", "resolved via who-dat fallback", "DNSSEC delegation state not exposed by upstream"). Paste-ready for analyst notes.


Limitations

Honest scoping. The actor is not a substitute for these things.

  • RDAP coverage varies by TLD. The curated table covers ~50 TLDs (all major gTLDs + most ccTLDs + popular new gTLDs). Less-common TLDs fall back to the who-dat backup. Anything truly unsupported surfaces explicit failureType: 'unsupported-tld' with recoveryPlan.rationale naming the right sibling actor.
  • Registrant data is often redacted. GDPR + ICANN privacy regulations mask registrant names / emails / addresses on most domains. registrant returns null; evidence.limitations flags the redaction. This actor cannot bypass redaction.
  • Domain availability is indicative, not definitive. RDAP 404 → available: true is a strong signal; reserved names + registry maintenance can produce false positives. Confirm with a registrar before purchasing.
  • lifecycleState.estimatedDaysToDrop uses ICANN-standard windows. 45-day grace + 30-day redemption + 5-day pending-delete. Most registries follow this; some ccTLDs (.de, .uk, .fr) vary. Verify with the registry before acting on a drop-day prediction.
  • No historical WHOIS data. Current registration only. For ownership history use a paid WHOIS history service.
  • No DNS / SSL / IP / ASN data. This is a WHOIS-layer actor only. Chain dns-record-lookup / crt-sh-search / ip-geolocation-lookup via actorGraph.next[] for those layers.
  • Maximum 1,000 domains per run. Split larger batches across multiple runs (re-use watchlistName for continuity). Cap-bound runs surface summary.truncated: true + truncatedReason.
  • Rate-limited upstream. Global ~120ms rate limiter protects RDAP infrastructure. Large batches take proportionally longer — runtime budget auto-clamps against Apify deadline to ensure partial-results emit.

Watchlist mode

Pass a watchlistName to convert this from an ad-hoc lookup tool into a monitoring product.

Run 1: baseline. Every record carries changeFlags: ['NEW'] + delta.type: 'new'. The actor persists a per-domain snapshot in the named KV store whois-domain-lookup-<watchlistName> including a bounded last-10-runs riskScore history.

Run 2+: delta detection. Every record carries delta.type (changed / recovered / degraded / unchanged) + delta.fieldsChanged[] + delta.headline + delta.previous. Change-driven alerts fire automatically (changed-registrar / changed-nameservers / lifecycle-degraded / recommendedAction-degraded).

Run 3+: trajectory activates. trajectory.riskTrend (worsening / improving / stable / volatile) + trajectory.stability + trajectory.changeVelocity from the bounded riskScore history. trajectory-worsening alerts fire when the trend crosses the threshold.

Pair watchlistName with emitAlertsOnly: true for the smallest possible monitoring payload.


Reference-run diff

Pass referenceRunId to diff against a specific prior run's snapshot (instead of the rolling watchlist baseline). Useful for post-incident recovery and known-good baselines.

{ "domains": [...], "referenceRunId": "1abc2def3ghi4jkl" }

Takes precedence over watchlistName when both are set.


Alerts

Stable alert types (additive-only within a major version):

Alert typeFires whenDefault severity
expired-domainPast expiry dateHIGH
critical-expiryExpiring within expiryCriticalDaysHIGH if ≤7d, else MEDIUM
weak-security-on-activeRegistered + weak security postureHIGH if riskScore ≥70, else MEDIUM
high-suspicion-signalssuspicionScore ≥ thresholdHIGH if ≥75, else MEDIUM
new-suspicious-domainRegistered <newDomainMaxAgeDaysMEDIUM if high suspicion tier, else LOW
lifecycle-degradedLifecycle phase ∉ {active, available}HIGH if pending-delete, else MEDIUM
shared-nameserver-clusterclusterMinMembers domains share NSLOW
changed-registrarRegistrar changed vs prior runMEDIUM
changed-nameserversNS changed vs prior runMEDIUM (HIGH on high-risk)
recommendedAction-degradedAction worsened vs prior runMEDIUM
trajectory-worseningMulti-run risk trend worseningMEDIUM

Override thresholds via the alertThresholds input object.


Output profiles

ProfileUse caseFields kept
minimalZapier / Make / n8n if-then rulesTier-1 routing fields + lifecycleState + alert
standard (default)SDR / analyst reviewTier-1 + Tier-2 interpretation
llmAI agent tool-callsDecision-relevant + whyThisMatters + executionHint + lifecycleState + registrarProfile + suspicionSignals + infrastructureProfile
fullAudit / debugEverything including raw RDAP fields

API & Integration

Python

from apify_client import ApifyClient

client = ApifyClient("YOUR_API_TOKEN")

run = client.actor("M7WwJi9RsIxqCtxsh").call(run_input={
    "domains": ["google.com", "stripe.com", "github.com"],
    "profile": "soc-triage",
    "watchlistName": "weekly-check"
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item.get("recordType") == "lookup":
        print(f"{item['domain']}: {item['recommendedAction']} ({item['riskLevel']}) — lifecycle:{item['lifecycleState']['phase']}")
    elif item.get("recordType") == "alert":
        print(f"ALERT [{item['severity']}]: {item['message']}")

JavaScript

import { ApifyClient } from "apify-client";

const client = new ApifyClient({ token: "YOUR_API_TOKEN" });

const run = await client.actor("M7WwJi9RsIxqCtxsh").call({
    domains: ["google.com", "stripe.com", "github.com"],
    profile: "soc-triage",
    watchlistName: "weekly-check"
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
items
    .filter((i) => i.recordType === "lookup")
    .forEach((i) => console.log(`${i.domain}: ${i.recommendedAction} (${i.riskLevel}) — lifecycle:${i.lifecycleState.phase}`));

Integrations

  • Zapier — branch on recommendedAction / lifecycleState.phase / decisionPosture enums directly.
  • Make — route by recordType to different downstream paths.
  • Google Sheets — pipe the Decisions view; sort by riskScore desc.
  • Webhooks — receive completion notifications + the summary record's decisionPosture for auto-routing.
  • GitHub Actions / CI — schedule daily runs with watchlistName; gate deploys on decisionPosture != 'act-now'.
  • Slack / Teams — drop alerts[] straight into a channel webhook; each alert's message is paste-ready.

Use in Dify

WHOIS Domain Lookup returns scored, classified, and recommended-action records as structured JSON — expired / critical / warning / healthy expiry tiers, active / auto-renew-grace / redemption / pending-delete / registry-hold / client-hold lifecycle phases, hardened / standard / weak security postures, register-now / renew-now / investigate / monitor / enable-eppLocks / enable-dnssec / trust-established / no-action recommended actions, plus a top-level decisionPosture (act-now / canary-recommended / monitor-only / no-action-required / no-call) your downstream Dify node branches on. Competitors return raw WHOIS text; this returns decisions.

Actor ID: ryanclinton/whois-domain-lookup

Sample input — phishing-detection decision pass:

{
    "domains": ["paypa1-auth.com", "micr0soft-login.net", "amaz0n-billing.org"],
    "profile": "phishing-detection",
    "outputMode": "decision",
    "outputProfile": "llm"
}

Dify if/else routing examples:

WHERE decisionPosture = "act-now" → escalate to security team + open ticket

WHERE recordType = "lookup" AND lifecycleState.phase = "pending-delete"
    → archive (domain dropping in ~5d)

WHERE recordType = "lookup" AND suspicionSignals.suspicionTier = "high"
    → route to SOC review queue

WHERE recordType = "alert" AND severity = "HIGH"
    → fire Slack webhook + page on-call

WHERE recordType = "lookup" AND recommendedAction = "renew-now"
    → schedule renewal task with expiryDate

WHERE delta.type = "degraded" OR trajectory.riskTrend = "worsening"
    → fire urgent alert

Opt-in modes Dify workflows can leverage: outputMode: 'decision' (single consolidated record), emitAlertsOnly: true (zero-noise monitoring), outputProfile: 'minimal' (smallest payload), profile: '<detection-profile>' (use-case bundle).

Structured action arrays are usable verbatim — no LLM rewriting. improvementSuggestions[], top3Actions[], executionHint, and alerts[] ship in shapes that drop straight into Dify multi-step flows, Slack blocks, or Jira/Linear ticket templates.


What this actor is NOT

Contrastive identity — sharper than "what it doesn't do". This is the decision engine for domain registration data; it is not:

  • NOT a raw WHOIS wrapper or WHOIS text scraper. The output is typed operational decisions, not raw WHOIS text. Compare with WhoisXML / Whoxy / RDAP.org → those return records; this returns decisions.
  • NOT a passive DNS / infrastructure-graph platform. No SecurityTrails / RiskIQ ambitions — only what's derivable from WHOIS + RDAP alone. Active DNS / passive DNS / SSL / IP / ASN are sibling-actor jobs surfaced via actorGraph.next[].
  • NOT a dashboard-centric threat-intel product. No DomainTools / Recorded Future analyst UI. Built for automation pipelines + agent tool-calls, not human investigation.
  • NOT a probabilistic / ML / "AI-powered" scoring engine. Every classification is a deterministic rule table with a pinned runManifest version. No model drift; no opaque scoring.
  • NOT an execution actor. This is a decision layer — no auto-renew, no auto-register, no auto-takedown side effects. Every recommendedAction ships with an executionHint for orchestration tools to act on; the actor never writes to your CRM / registrar / tracker.
  • NOT a registrant-deanonymisation tool. GDPR + ICANN privacy redactions are honoured. registrant: null is the correct answer when the registry redacts.
  • NOT a paid WHOIS history service. Current registration record only; no ownership-change archaeology.
  • NOT a domain-valuation engine. available + expiryStatus + ageTier ship; pricing is the consumer's call.
  • NOT a registrar reputation labeller. registrarProfile.riskTier is a neutral curated-table tier + jurisdiction + factual notes[]. No "abuse-associated" / "bulletproof" labels — defamation-safe by design.

The category claim, plainly: operational domain intelligence. Lightweight + deterministic + composable + automation-native + replayable. That's the unfair position.


Responsible use

  • Respect public RDAP infrastructure. Global rate limiter (~120ms = 8 req/sec) is designed to be polite.
  • Do not use for spam. Registrant extraction for unsolicited bulk email violates ICANN ToS + likely anti-spam law.
  • Comply with GDPR + privacy regulations. Registrant data is subject to data protection laws.
  • Verify before acting. Availability checks and registry status are point-in-time.

FAQ

Does this require an API key? No.

Which TLDs are supported? All TLDs with RDAP servers registered with IANA — ~50 curated + extended by the who-dat backup.

Can I check if a domain is available? Yes. RDAP 404 → available: true.

Why is registrant null? GDPR + ICANN privacy redactions.

How does watchlist mode work? Pass watchlistName. Run 1 baselines, run 2+ emits deltas, run 3+ activates trajectory.

Can I diff against a specific prior run? Yes. Pass referenceRunId.

Can I run this on a schedule? Yes. Pair Apify Schedules with watchlistName + emitAlertsOnly: true.

What's the difference between mode and profile? Mode picks the scoring lens; profile picks the use-case bundle. They compose — you can set both.

How does the suspicion-signals detection work? Pure regex + Shannon-entropy math over the SLD. Homoglyph (0/o, 1/l, rn/m, vv/w, cl/d, cyrillic, punycode), security-keyword (login, auth, verify, secure, etc.), high entropy (random-looking SLDs), numeric-pattern (3+ digits), suspicious hyphens (3+). No LLM. Documented in code.

How accurate is lifecycleState.estimatedDaysToDrop? Heuristic based on ICANN-standard registry lifecycle windows (45-day grace + 30-day redemption + 5-day pending-delete). Most registries follow this; some ccTLDs vary. Verify with the registry before acting on a drop-day prediction.

Can I export CSV? Yes. Apify dataset supports JSON, CSV, Excel, XML, RSS.

What happens if both data sources fail? error set with a failureType enum (transient / unsupported-tld / unknown). NEVER charged a PPE event.

What about restricted-permission tokens? Probed at run start. Restricted tokens skip named-KV writes (SUMMARY, watchlist) with a clear warning. The lookup itself still works.


Related actors

ActorDescription
DNS Record LookupRetrieve A, AAAA, MX, TXT, CNAME, NS, SOA records.
SSL Certificate SearchFind all SSL/TLS certificates issued for a domain.
IP Geolocation LookupGeolocate IPs to country, city, ISP, organisation.
Website Tech Stack DetectorDetect technologies running on a website.
Brand Protection MonitorMonitor for brand impersonation + counterfeit listings.
URL UnshortenerExpand shortened URLs before WHOIS lookup.
Last verified: March 27, 2026

Ready to try WHOIS Domain Lookup: Operational Domain Intelligence?

Run it on your own Apify account. Apify offers a free tier with $5 of monthly credits.

Open on Apify Store