# Domain WHOIS / RDAP Lookup (`pontio/domain-whois`) Actor

Registration data for a domain via RDAP: registrar, status, and the created/updated/expires dates.

- **URL**: https://apify.com/pontio/domain-whois.md
- **Developed by:** [Gabor Molnar](https://apify.com/pontio) (community)
- **Categories:** Developer tools, SEO tools, Lead generation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 domain checkeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Domain WHOIS / RDAP Lookup

Look up who a domain is registered with, when it was created, when it expires, what its EPP statuses and nameservers are, and whether it is registered at all. Pass one domain or a list of up to 1,000 in a single run, and get a record back for each. The answer comes from the registry's own RDAP service, and when no registry can answer, the Actor says so instead of guessing.

### What you get

One dataset item per domain you send. Here is one of them, for input `example.com`:

```json
{
  "domain": "example.com",
  "registered": true,
  "reasonCode": "registered",
  "registrar": "RESERVED-Internet Assigned Numbers Authority",
  "creationDate": "1995-08-14T04:00:00Z",
  "expirationDate": "2027-08-13T04:00:00Z",
  "nameServers": ["ELLIOTT.NS.CLOUDFLARE.COM", "HERA.NS.CLOUDFLARE.COM"],
  "status": [
    "client delete prohibited",
    "client transfer prohibited",
    "client update prohibited"
  ]
}
```

### Use it when

- You are checking whether a name has a registration record before you try to buy it.
- You are watching expiry dates across a portfolio of domains.
- You are scoring a lead or a signup domain, where a name registered last week means something different from one registered in 2003.
- You need the EPP status codes, for example to see that a domain is locked, on hold, or in a redemption period.
- An agent is enriching a list of domains and needs one typed record per name.

### Input

| Field | Type | Required | What it does |
| --- | --- | --- | --- |
| `domains` | array of strings | yes | The domains to look up, up to 1,000 per run. A URL or an email address is accepted and reduced to its host, so `https://example.com/cart` and `jane@example.com` both mean `example.com`. A subdomain is kept as written and comes back `possible_subdomain`. Internationalized names are converted to their punycode form. |

Each distinct domain gets one dataset item and one charge. `Example.com`, `https://example.com/` and `example.com.` are the same domain, so a list that repeats one is answered and charged once for it.

### Output

| Field | What it means |
| --- | --- |
| `registered` | `true`, `false`, or `null` when the lookup was inconclusive. Read `reasonCode` before you act on it. |
| `reasonCode` | `registered`, `not_registered`, `no_rdap_service`, `possible_subdomain`, or `invalid_domain`. |
| `registrar` | The sponsoring registrar's name, from the RDAP registrar entity. |
| `creationDate`, `expirationDate` | ISO 8601 timestamps from the registry's own event records. |
| `nameServers` | Nameservers as the registry holds them, which can differ from what DNS currently answers. |
| `status` | EPP status codes, for example `client transfer prohibited`. |

Registrant contact details are not returned. Since GDPR, registries redact them, and this Actor does not claim to have data it cannot get.

### Pricing

Pay per event, $3.00 per 1,000 lookups ($0.003 each), charged on the `domain-whois-checked` event.

A conclusive answer is charged whichever way it goes. "This domain is registered to X" and "the registry has no registration record for this domain" are both real answers. No record is not a guarantee the name can be bought: registries reserve, block and premium-price names that have no record. An inconclusive lookup is free:

| `reasonCode` | Charged |
| --- | --- |
| `registered` | yes |
| `not_registered` | yes |
| `no_rdap_service` (the TLD runs no RDAP service, so nobody can be asked) | no |
| `possible_subdomain` (a registry holds no record of a subdomain) | no |
| `invalid_domain` (an IP, a bare TLD, `localhost`, a malformed name) | no |
| `resolve_error` (the registry lookup failed or was rate limited) | no |

If you set a maximum total charge for a run, the Actor stops as soon as that limit is reached instead of working for free. Items after that point are left out of the dataset and not charged, and the run log says how many; submit them in a new run.

### Why a `not_registered` answer is trustworthy

A wrong "no registration record" is the most expensive mistake a lookup like this can make, so the Actor runs two checks before it says so.

Before any lookup, the name is checked against the public suffix list, ICANN section only. `foo.co.uk` is a registrable domain and gets a real verdict. `news.bbc.co.uk` is not, and a registry's 404 for it looks exactly like a 404 for a name nobody has taken. That is reported as `possible_subdomain` and charged nothing.

When a registry returns a bare 404 with no explanation, the Actor consults IANA's RFC 9224 registry of which TLDs publish RDAP at all before reading that 404 as `not_registered`. If IANA has no RDAP entry for the TLD, the result is `no_rdap_service`, not a claim that no record exists. Around 300 TLDs fall in that group today.

### How it works

The Actor queries RDAP over HTTPS, the structured JSON successor to port-43 WHOIS, and finds each TLD's server through the standard bootstrap. Requests retry on 429 and 5xx responses and on transport failures, honor a `Retry-After` header, and carry a per-attempt deadline so a hung registry cannot hold your run open.

### One item per domain

Domains are looked up in the order you send them, each written as its own dataset item. There is no start fee, so a list of 1,000 domains pays one container start for the whole run instead of one per name:

```js
const run = await client.actor('pontio/domain-whois').call({
  domains: ['example.com', 'example.org'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

If you set a maximum charge for the run and the batch reaches it, the run stops there rather than carrying on with work it cannot charge for. A run that reaches its timeout stops the same way. Either way the items already written stay in the dataset, and each names what it answers, so you can re-run the remainder.

Need DNS records, MX, and SPF/DMARC/DKIM grading alongside the registration data? Use Domain Analyzer, which returns all of it as one report.

# Actor input Schema

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

Domain names to look up, e.g. example.com (max 1000 per run). A URL, an email address, a www. prefix, mixed case or a trailing dot are normalized first. Subdomains are preserved and can return possible\_subdomain. Inputs that normalize to the same domain are answered and charged once.

## Actor input object example

```json
{}
```

# Actor output Schema

## `results` (type: `string`):

One item per input, in this run's default dataset.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("pontio/domain-whois").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("pontio/domain-whois").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 '{}' |
apify call pontio/domain-whois --silent --output-dataset

```

## MCP server setup

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

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/J8jSidMJJDuT5rW8B/builds/N3p8eMtZ2qVwrWb6v/openapi.json
