# Bulk WHOIS Lookup: Registrar, Age & Expiry (`khadinakbar/bulk-whois-lookup`) Actor

Check up to 100 domains through structured RDAP. Get registrar, registration and expiry dates, calculated age and expiry risk, nameservers, domain status, and source URLs in one bounded bulk run.

- **URL**: https://apify.com/khadinakbar/bulk-whois-lookup.md
- **Developed by:** [Khadin Akbar](https://apify.com/khadinakbar) (community)
- **Categories:** Developer tools, SEO tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 completed rdap domain lookups

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Bulk WHOIS Lookup: Registrar, Age & Expiry

Bulk WHOIS Lookup checks up to 100 domains through public RDAP and returns one structured dataset record per completed domain. It gives domain operations, SEO teams, and AI agents the registrar, registration date, expiry date, calculated domain age, days until expiry, nameservers, status codes, and the source URL needed to act on a current registration snapshot.

### Best fit

Use this Actor to prioritize a portfolio renewal review, enrich a vetted company-domain list, or confirm where a group of domains is registered before a migration. It is designed for a concise, source-linked batch of domains rather than a broad DNS inventory. After this check, pass domains approaching expiry into your renewal workflow or feed selected domains into a focused website, SEO, or contact-research tool.

### From domain list to renewal queue

An operations lead starts with a list of customer domains, then runs a small batch at the default parallelism. Next, the lead filters `expiryStatus` for `expires_soon`, compares registrar names, and exports the source-linked records into the team’s renewal queue. The registration date and calculated age stay with the record, so later reviewers can distinguish a current RDAP snapshot from an older export.

### Quick start input

```json
{
  "domains": ["example.com", "apify.com", "github.com"],
  "concurrency": 3
}
```

`domains` accepts registrable domains and bare HTTPS domain URLs. The Actor normalizes case, trailing dots, and internationalized names before contacting RDAP.

### Input reference

| Field | Type | What it controls |
| --- | --- | --- |
| `domains` | string\[] | One to 100 domains for the batch. Every completed public RDAP response becomes one dataset record. |
| `concurrency` | integer | One to five parallel public RDAP requests. Start at the default of three for a predictable public-source workflow. |

### What data you receive

Each dataset item represents one completed RDAP check. A `found` item contains fields the registry disclosed; a `not_found` item keeps the exact domain and provenance while its unavailable registration fields remain `null`. `expiryStatus` is calculated from the returned expiry date at collection time: `expires_soon` covers 0–30 days, and `unknown` means the registry did not supply an expiry date.

| Field | Meaning |
| --- | --- |
| `registrarName`, `registrarIanaId` | Registrar identity when exposed by the RDAP registrar entity. |
| `createdAt`, `domainAgeDays` | Registration timestamp and whole UTC days since registration. |
| `expiresAt`, `daysUntilExpiry`, `expiryStatus` | Expiry timestamp and calculated renewal priority. |
| `nameservers`, `domainStatuses` | Current registry nameservers and status codes supplied by RDAP. |
| `rdapSourceUrl`, `registryRdapUrl`, `collectedAt` | Public provenance and collection time for the snapshot. |

```json
{
  "runId": "abc123",
  "domain": "example.com",
  "lookupStatus": "found",
  "registrarName": "RESERVED-Internet Assigned Numbers Authority",
  "registrarIanaId": "376",
  "createdAt": "1995-08-14T04:00:00.000Z",
  "expiresAt": "2027-08-13T04:00:00.000Z",
  "domainAgeDays": 11337,
  "daysUntilExpiry": 348,
  "expiryStatus": "active",
  "nameservers": ["a.iana-servers.net", "b.iana-servers.net"],
  "domainStatuses": [],
  "rdapSourceUrl": "https://rdap.org/domain/example.com",
  "registryRdapUrl": null,
  "collectedAt": "2026-08-30T12:00:00.000Z",
  "provider": "RDAP via rdap.org"
}
```

### Terminal outcomes

Read `OUTPUT` or `RUN_SUMMARY` together with the dataset. `COMPLETE` means every planned domain persisted, `PARTIAL` retains completed records and lists the domains requiring a fresh pass, `VALID_EMPTY` means completed lookups returned no registration records, and `INVALID_INPUT` gives a direct domain-format correction. A public-source outage is reported as `UPSTREAM_FAILED` with a detailed summary for a later retry.

### Use through the API

```bash
curl "https://api.apify.com/v2/acts/khadinakbar~bulk-whois-lookup/runs?token=$APIFY_TOKEN" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"domains":["example.com","apify.com"],"concurrency":2}'
```

Retrieve the default dataset after the run, then read `OUTPUT` and `RUN_SUMMARY` before triggering renewal or registrar-change actions. Keep `rdapSourceUrl` and `collectedAt` with downstream records for a verifiable collection trail.

### Use with AI agents through Apify MCP

> Check `example.com`, `apify.com`, and `github.com` with Bulk WHOIS Lookup. Return the registrar, registration age, expiry date, and days until expiry for each completed record. Read the terminal outcome first, preserve the RDAP source URL and collection time, and label unknown expiry fields as registry-unavailable rather than an expiry risk.

An AI agent can start with a small, specific domain list and use the compact terminal output to decide whether a fresh retry or a downstream renewal review is appropriate.

### Pricing

Bulk WHOIS Lookup uses Pay per event plus Apify platform usage. An Actor-start event applies once per run, and a primary event applies after one completed RDAP domain record is persisted, including a truthful `not_found` response. The current live Pricing tab is the source of truth for event prices and platform-usage configuration. Set the run’s maximum charge budget before large batches and use the 100-domain input boundary to plan total events.

### Best results

Start with domains you are authorized to review and use a small batch to confirm the registry coverage for your TLD mix. Keep the default concurrency for a steady public RDAP route, validate the `lookupStatus` before treating a record as a registration result, and schedule a fresh run when a renewal decision needs a current source snapshot.

### Builder's note

I designed this Actor around structured RDAP events rather than screen-scraped WHOIS text. That keeps age and expiry calculations transparent: the output preserves the original event timestamps, the calculation fields, and the public source URL, while a registry-specific missing field remains visible as `null` instead of being guessed.

### Focused standalone workflow

This Actor is designed as a focused standalone workflow for a bounded registrar, age, and expiry check. It works as the first verification step before a team creates a renewal queue, enriches an approved domain list, or opens a registrar migration project.

### Responsible use

Use public registration data for lawful, authorized domain operations and comply with applicable laws, registry policies, and service terms. Treat each record as a timestamped public-source snapshot and confirm critical renewal decisions with the responsible registrar.

# Actor input Schema

## `domains` (type: `array`):

One to 100 registrable domains, such as `example.com` or `apify.com`. Bare HTTPS domain URLs such as `https://example.com/` are accepted, but paths, ports, queries, IP addresses, and duplicate domains are rejected or de-duplicated. Each completed RDAP response becomes one dataset record.

## `concurrency` (type: `integer`):

How many public RDAP lookups may run at once. Defaults to 3 and caps at 5 to keep the public registry route reliable. This changes speed only; it does not change the returned fields or the one-record-per-completed-domain billing rule.

## Actor input object example

```json
{
  "domains": [
    "example.com",
    "apify.com",
    "github.com"
  ],
  "concurrency": 3
}
```

# Actor output Schema

## `domainExpiryChecks` (type: `string`):

One completed RDAP record per processed domain, including a truthful not-found record when the public route has no registration object.

## `runSummary` (type: `string`):

Detailed terminal outcome, provider diagnostics, counts, and charges.

## `compactOutput` (type: `string`):

Stable terminal output for agents and automations.

## `lastRunSummary` (type: `string`):

Compatibility alias for the detailed terminal record.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "domains": [
        "example.com",
        "apify.com",
        "github.com"
    ],
    "concurrency": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("khadinakbar/bulk-whois-lookup").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "domains": [
        "example.com",
        "apify.com",
        "github.com",
    ],
    "concurrency": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("khadinakbar/bulk-whois-lookup").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "domains": [
    "example.com",
    "apify.com",
    "github.com"
  ],
  "concurrency": 3
}' |
apify call khadinakbar/bulk-whois-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,khadinakbar/bulk-whois-lookup"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/mg22nSu28veBcIJ54/builds/aAtyDukL2QpQiF0wC/openapi.json
