# Domain Lookup: DNS Records & Availability Checker (`stego-bot/rdap-domain-lookup`) Actor

Bulk domain lookup for any TLD, incl. .com: live DNS records (A, AAAA, MX, TXT, NS) and an availability hint. Most rows are DNS-only. Registrar, expiry and status come from official registry RDAP only for 8 supported TLDs (e.g. .fi, .ec, .ht).

- **URL**: https://apify.com/stego-bot/rdap-domain-lookup.md
- **Developed by:** [Stego](https://apify.com/stego-bot) (community)
- **Categories:** Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 domain looked ups

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 Lookup: DNS Records & Availability Checker

Check one domain or up to 10,000 in a single run. You get **one clean JSON object per domain** with:

- live **DNS records**: A, AAAA, MX, TXT and NS
- a **registered / likely-available hint**
- clear statuses that scripts and AI agents can rely on

This works for **every TLD, including .com, .net and .org**.

**Expect most rows to be DNS-only.** Registrar, created/expiry dates, EPP status and DNSSEC come from the
registry's official RDAP service, and are returned only for **8 small TLDs** whose registry terms we read and found
no written ban on this use: **.fi, .ec, .ht, .cv, .fj, .pg, .ss and .vi** (details below).

Almost every other registry, including Verisign (.com/.net), PIR (.org), Identity Digital, GoDaddy Registry,
CentralNic, Nominet and Google Registry, bans automated or commercial use in writing, so it is never queried. Those
domains (in practice, the large majority of what you will look up) get DNS data and the DNS-based availability hint
only, with registry fields set to `null`.

No WHOIS scraping, no website scraping, no API key.

### What it does

- **DNS records** (on by default). A, AAAA, MX (priority and exchange), TXT (chunks joined) and NS, using standard
  DNS queries through the platform's resolver. Each row also gets a `dnsStatus`: `NOERROR`, `NXDOMAIN`, `SERVFAIL` or
  `TIMEOUT`.
- **Availability hint** (switch on `checkAvailability`). Adds `likelyAvailable`, `availabilityBasis` (`dns`, `rdap`
  or `rdap+dns`) and a plain-English `availabilityNote`.
- **Registry RDAP data, supported TLDs only.** Adds `registrar`, `createdAt`, `updatedAt`, `expiresAt`,
  `daysUntilExpiry`, `statusCodes`, `nameservers` and `dnssec`. The registry RDAP server is found through the
  official IANA RDAP bootstrap. Unsupported TLDs instead report `rdapServer`, `registryOperator` and
  `registryTermsUrl`, so you can see why.
- **Normalises messy input.** Accepts:

  - plain domains, URLs (`https://www.example.com/page`) and emails (`name@example.com`)
  - IDNs (`bücher.de`) and punycode
  - subdomains, which are reduced to the registered domain with the Public Suffix List
    (`shop.example.co.uk` → `example.co.uk`)

  Duplicates are merged and charged once.
- **Never fails silently.** Every input entry gets a row with a `status`, plus a readable `error` when relevant.
- **Crash-safe.** Progress is saved after every batch of 25 results. If a run restarts, it never returns or charges
  a domain twice.

### Polite to registries

- **One request at a time** per registry server, at most 1 request per second by default (the maximum allowed is 2).
- `Retry-After` is honoured on HTTP 429/503:
  - short waits: the Actor waits, retries and slows down
  - long waits: that server's remaining domains are returned as `rate-limited` and are not charged
- A server that keeps failing is paused for the rest of the run.
- **Caching.** Registry answers are reused, so a registry is not asked the same question twice:
  - within a run, each RDAP URL is requested at most once
  - across runs, answers are kept for `cacheHours` (default 24 h) in a key-value store named
    `rdap-domain-lookup-cache` on *your* Apify account
  - only non-contact fields are stored (registrar name, IANA ID and URL, dates, status codes, nameservers,
    DNSSEC). **No contact data is stored at all, not even the registrar's abuse email or phone.** A row served from
    the cache therefore has `registrarAbuseEmail` and `registrarAbusePhone` set to `null`; set `cacheHours` to 0 if
    you need them on every row.
  - "not found" answers are reused for at most 1 hour
  - rows served from the cache have `rdapFromCache: true` and `rdapCachedAt`
  - DNS is always live
- Redirects are followed manually: HTTPS only, at most 3 hops, and every hop must also be a supported registry server.
  A refused redirect is never requested, and the row is returned as `error` and not charged.

### Statuses

| `status` | Meaning | Charged? |
|---|---|---|
| `registered` / `not-found` | Supported TLD: the registry RDAP answered (record / HTTP 404) | yes |
| `excluded-by-registry-terms` | Registry not queried: its RDAP terms restrict this use, or could not be confirmed. DNS fields are filled. | yes, if DNS gave a clean answer |
| `no-rdap-server` | The TLD has no RDAP service in the IANA bootstrap (e.g. .de, .io, .co). DNS fields are filled. | yes, if DNS gave a clean answer |
| `terms-not-reviewed` | A registry server added to IANA after our review; not queried. DNS fields are filled. | yes, if DNS gave a clean answer |
| `rate-limited` / `error` | Lookup failed (see `error` and `retryAfterSeconds`), including a registry redirect that was refused (non-HTTPS, too many hops, or pointing to a server that is not supported) | **no**, never, even if DNS data is present |
| `invalid-input` | Not a usable domain (see `error`) | no |
| `not-processed` | The run's max total charge was reached first | no |

Each row has `billable: true|false`, so you can see exactly which rows were charged. A clean DNS answer means
either every requested record type resolved (`NOERROR`) or the name does not exist (`NXDOMAIN`).

### Availability caveat

- **DNS only** (.com, .net, .org and most TLDs):
  - `NOERROR`: the name exists, so it is **registered**.
  - `NXDOMAIN`: a **weak** hint. Registered names with no nameservers, names on hold, and reserved or premium names
    also return `NXDOMAIN`.
- **With registry RDAP** (supported TLDs): an HTTP 404 means the registry has no record, so the name is *likely*
  available. If DNS still shows the name, it is reported as probably registered (`rdap+dns`).
- **Neither is a guarantee.** Always confirm with a registrar before buying.

### Supported TLDs for registry data (review of 21 Oct 2026)

.cv, .ec, .fi, .fj, .ht, .pg, .ss and .vi. Second-level names under them, like .com.ec and .com.fj, are covered
too. The .ss registry limits lookups to 15 per minute "to prevent 'data mining'", so .ss names are
always paced at one request every 4 seconds.

Not supported (registry never queried; DNS data only), with the reason and quotes in TOS-NOTES.md:

- .com, .net, .org, .info, .biz, .uk, .ca, .nl, .au, .br, .in, .pl, .ng and almost all new gTLDs: the registry bans
  the use in writing (on any channel: RDAP notice, WHOIS terms or website terms).
- .ly, .zm, .ml, .lb, .gs, .nf, .cx, .gy, .rw, .uz, .hn, .as and .ke: also written bans, found in the October 2026
  re-review.
- .fr, .tz and .рус: the registry's terms could not be read or verified (site offline / terms document missing /
  claimed ban not locatable), so they are treated as not supported until confirmed.
- .de, .io, .co: these TLDs have no RDAP service.

### Input

| Field | Default | Description |
|---|---|---|
| `domains` | required | Domains, URLs or emails; up to 10,000 per run |
| `reduceToRegisteredDomain` | `true` | Subdomain → registered domain. When off, subdomains are `invalid-input`. |
| `includeDns` | `true` | Resolve DNS records |
| `dnsRecordTypes` | all five | Any of `A`, `AAAA`, `MX`, `NS`, `TXT` |
| `checkAvailability` | `false` | Adds the availability hint (see the caveat above) |
| `requestsPerSecondPerServer` | `1` | Pacing per supported registry server (0.1–2) |
| `maxRetryWaitSecs` | `30` | Longest `Retry-After` to wait for. Longer waits mark that server's domains as `rate-limited`. |
| `requestTimeoutSecs` | `20` | Per-request timeout for RDAP and DNS |
| `maxConcurrency` | `10` | Domains processed in parallel (per-server limits still apply) |
| `cacheHours` | `24` | How long registry answers are reused across runs (0 turns the cross-run cache off) |

Example inputs:

```json
{ "domains": ["apify.com", "https://www.bbc.co.uk/news", "support@traficom.fi"] }
```

```json
{ "domains": ["my-startup-name.com", "my-startup-name.fi"], "checkAvailability": true }
```

### Output (one item per unique domain)

See `samples/output-sample.json` for real output. Summary of the main fields:

- **DNS:** `aRecords`, `aaaaRecords`, `mxRecords`, `txtRecords`, `dnsNameservers`, `dnsStatus`, `dnsError`
- **Availability:** `likelyAvailable`, `availabilityBasis`, `availabilityNote`
- **Registry** (supported TLDs; `null` otherwise): `registrar`, `registrarIanaId`, `registrarUrl`,
  `registrarAbuseEmail`, `registrarAbusePhone` (live answers only, never cached), `createdAt`, `updatedAt`, `expiresAt`, `daysUntilExpiry`, `statusCodes`,
  `eppStatusCodes`, `nameservers`, `dnssec`, `rdapUrl`, `rdapFromCache`, `rdapCachedAt`
- **Why not:** `rdapServer`, `registryOperator`, `registryTermsUrl`, `error`

Dates are always `YYYY-MM-DDTHH:MM:SSZ` (UTC). Fields you switch off (DNS, availability) are left out instead of
being set to `null`.

A run summary is saved under the `OUTPUT` key. It includes `byStatus`, `chargedLookups`, `rdapRequestsSent`,
`rdapServersQueried`, `rdapCacheHits` and `bootstrapPublished`.

### Personal data

None. Registrant, admin and tech contacts are never returned or cached. The only contact data returned is the
registrar's role-based abuse email and phone, when the registry publishes them, and only on live registry answers:
they are never written to the cache.

### Pricing

Pay per event: a small start fee, plus a fee per domain on `billable` rows only.

- **The per-domain fee is the same whether or not registry data is returned.** For most domains (all .com, .net,
  .org and most other TLDs) a billable row contains DNS records and the DNS availability hint only. Registry data is
  a bonus for the 8 supported TLDs above.
- A DNS-only row is charged only if DNS gave a clean answer (every record type `NOERROR`, or `NXDOMAIN`).
- **Free:** `error` and `rate-limited` rows (including refused redirects), even when DNS data is present; invalid,
  duplicate and not-processed rows; DNS-only rows with a DNS failure. The run respects your "Max total charge" setting; domains beyond it are returned as
  `not-processed`.

### Data sources and terms

- IANA RDAP bootstrap registry: `https://data.iana.org/rdap/dns.json` (RFC 9224).
- DNS: the public Domain Name System, using standard resolver queries.
- Registry RDAP servers: only those whose terms or help text we read and found no written ban. Per-server reasoning
  and quotes are in TOS-NOTES.md and `src/registry_terms.json`.

# Actor input Schema

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

One or more domains, e.g. example.com. URLs (https://www.example.com/page) and email addresses (name@example.com) are accepted and reduced to the domain. Internationalised names (bücher.de) and punycode (xn--bcher-kva.de) both work. Duplicates are looked up once. Up to 10,000 per run.

## `reduceToRegisteredDomain` (type: `boolean`):

Turn subdomains like www.shop.example.co.uk into the registered domain (example.co.uk) using the Public Suffix List. When off, subdomains are reported as invalid input instead.

## `includeDns` (type: `boolean`):

Resolve live A, AAAA, MX, TXT and NS records with standard DNS queries (the platform's recursive resolver). Works for every TLD, including .com, .net and .org.

## `dnsRecordTypes` (type: `array`):

Which DNS record types to resolve when DNS is on. Allowed: A, AAAA, MX, NS, TXT.

## `checkAvailability` (type: `boolean`):

Adds likelyAvailable, availabilityBasis and availabilityNote. For most TLDs (including .com, .net, .org) the hint is based on DNS only: NXDOMAIN is a WEAK signal. For the TLDs whose registry RDAP is supported (e.g. .fi, .ec, .ht), a registry 404 means LIKELY available. This is not a guarantee either way: reserved, premium, on-hold or just-deleted names can look free. Confirm with a registrar before buying.

## `requestsPerSecondPerServer` (type: `number`):

Polite pacing for each supported registry RDAP server (one request in flight per server, at most this many per second). The Actor slows down on HTTP 429/503 and honours Retry-After.

## `maxRetryWaitSecs` (type: `integer`):

If a server says 'retry after N seconds' and N is at most this value, the Actor waits and retries. If N is longer, the remaining domains on that server are returned with status rate-limited and retryAfterSeconds, and are not charged.

## `requestTimeoutSecs` (type: `integer`):

Timeout for each RDAP or DNS request.

## `maxConcurrency` (type: `integer`):

How many domains are processed at the same time across all servers. Per-server pacing still applies.

## `cacheHours` (type: `integer`):

Registry (RDAP) answers are cached in a key-value store named "rdap-domain-lookup-cache" on your own Apify account and reused for this long, so registry servers are not asked the same question again. Only non-contact fields are stored (registrar name, IANA ID and URL, dates, status codes, nameservers, DNSSEC). No contact data is stored, not even the registrar's abuse email or phone, so rows served from the cache have no registrarAbuseEmail/registrarAbusePhone. "Not found" answers are reused for at most 1 hour. DNS is always live. Set 0 to turn the cross-run cache off.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "wikipedia.org"
  ],
  "reduceToRegisteredDomain": true,
  "includeDns": true,
  "dnsRecordTypes": [
    "A",
    "AAAA",
    "MX",
    "NS",
    "TXT"
  ],
  "checkAvailability": false,
  "requestsPerSecondPerServer": 1,
  "maxRetryWaitSecs": 30,
  "requestTimeoutSecs": 20,
  "maxConcurrency": 10,
  "cacheHours": 24
}
```

# Actor output Schema

## `domains` (type: `string`):

Dataset with one item per unique input domain (including invalid entries).

## `summary` (type: `string`):

OUTPUT record with status counts, registry requests and throttling for this run.

# 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": [
        "apify.com",
        "wikipedia.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("stego-bot/rdap-domain-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": [
        "apify.com",
        "wikipedia.org",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("stego-bot/rdap-domain-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": [
    "apify.com",
    "wikipedia.org"
  ]
}' |
apify call stego-bot/rdap-domain-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,stego-bot/rdap-domain-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/fzCMbVreHjTSt8rAn/builds/GSw7xxtlSuo4caeNb/openapi.json
