# RDAP Domain Lookup and Expiry Checker (`gridlinetools/rdap-domain-lookup`) Actor

Query RDAP registries for a list of domains and get registrar, creation and expiry dates, EPP statuses, name servers and DNSSEC in one table. Structured JSON from the registry, not WHOIS text. Unsupported or invalid domains are reported but not billed.

- **URL**: https://apify.com/gridlinetools/rdap-domain-lookup.md
- **Developed by:** [Gridline Tools](https://apify.com/gridlinetools) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 domain lookups

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

## RDAP Domain Lookup and Expiry Checker: registrar, dates, statuses, name servers and DNSSEC for a list of domains

Give the Actor a list of domain names and it queries the responsible RDAP server for each one, returning a single record per domain with the registrar, registration and expiry dates, EPP statuses, name servers and DNSSEC flag. RDAP is the structured, JSON based successor of WHOIS (RFC 7480, 7484, 9082, 9083), so the fields come back typed and ready for a spreadsheet instead of as free text. One plain GET request per domain, no browser, no proxy, no page content.

### What it is good for

- **Expiry monitoring for a domain portfolio:** an MSP or IT team watching 100–300 client domains gets `expiresAt` and `daysToExpiry` for each one in a single run; filter the dataset for `daysToExpiry < 30` and alert.
- **A building block for automations:** one normalized JSON record per domain (registrar, dates, statuses, name servers, DNSSEC) with the IANA bootstrap, redirects and registry differences handled for you; no WHOIS parsing, no port 43.
- **Registry state checks:** see whether a domain is `registered` or `not-found` at the registry and which EPP statuses it carries (`client transfer prohibited`, `pending delete`, `redemption period`…). `not-found` means the registry does not know the name; it does **not** mean the name is available to register.
- **Domain age as a risk signal:** `createdAt` for vendor or counterparty screening, without any registrant personal data.

### How to use it

1. Open the Actor and paste your list of domains into the `domains` field, one per line (or pass a JSON array through the API).
2. Adjust `timeoutSecs` and `maxConcurrency` if you need to; the defaults work for most runs.
3. Click Start. When the run finishes, the status message reads `Processed N of M domains (X with errors, Y not charged)`.
4. Download the results from the Dataset tab as JSON, CSV or Excel, or read them through the API.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `domains` | list of strings | `example.com`, `example.net` | Domain names (or URLs, the host is extracted), one per line; exact duplicates skipped; max 1,000 per run |
| `timeoutSecs` | integer 1–60 | 15 | Maximum time per domain, including redirects |
| `maxConcurrency` | integer 1–50 | 10 | Domains looked up in parallel (10 tested on 50 `.com` domains without rate limiting; lower it if `rate-limited` records appear) |

### Output

One dataset record per input domain. Common fields:

| Field | Type | Meaning |
|---|---|---|
| `inputValue` | string | Domain (or URL) as provided, trimmed; credentials masked as `user:***@host`, also without a scheme (a password containing a raw `/`, `\`, `?` or `#` is not recognized as credentials) |
| `error` | string or null | `INVALID_DOMAIN`, `UNSUPPORTED_TLD`, `TIMEOUT`, `NETWORK_ERROR`, `TOO_MANY_REDIRECTS`, `BAD_REDIRECT`, `RATE_LIMITED`, `RDAP_ERROR` (none of them charged) or `null` |
| `billable` | boolean | `true` when the record was charged (`state` is `registered` or `not-found`); `false` for every record with an error, written free of charge |
| `errorMessage` | string or null | Human-readable reason |
| `checkedAt` | string | ISO 8601 timestamp |

Domain-specific fields:

| Field | Type | Meaning |
|---|---|---|
| `domain` | string | Normalized domain name (lowercase, punycode for IDNs, no leading `www.`); same as `inputValue` for invalid domains |
| `tld` | string or null | Last label of the normalized domain (e.g. `com`, `pl`); `null` for `INVALID_DOMAIN` |
| `state` | string or null | `registered`, `not-found`, `unsupported-tld`, `rate-limited`, `error`; `null` only for `INVALID_DOMAIN` |
| `registrar` | string or null | Registrar name from the RDAP response (first entity with role `registrar`); `null` when absent or empty |
| `registrarIanaId` | string or null | IANA Registrar ID from the same entity; `null` when absent |
| `createdAt` | string or null | Registration date (ISO 8601) from the first `registration` event |
| `updatedAt` | string or null | Last-changed date from the first `last changed` event |
| `expiresAt` | string or null | Expiration date from the first `expiration` event; may be `null` even for registered domains (e.g. `.pl` registry) |
| `daysToExpiry` | number or null | Full days until expiry: `Math.floor((expiresAt − now) / 86400000)`; negative for past dates; `null` when `expiresAt` is missing or unparseable |
| `statuses` | string\[] | Domain status codes (lowercase, no duplicates) from the RDAP response; may be empty for some registries (e.g. `.pl`) |
| `nameservers` | string\[] | Name server hostnames (lowercase, no duplicates, trailing dot removed) |
| `dnssec` | boolean or null | Whether DNSSEC is signed (`secureDNS.delegationSigned`); `null` when absent from the response |
| `rdapServer` | string or null | Base URL of the RDAP server used (from the IANA bootstrap, with trailing `/`); `null` for `INVALID_DOMAIN` and `unsupported-tld` |
| `rdapUrl` | string or null | URL of the last RDAP request made (after all redirects); `null` when no request was made |
| `httpStatus` | number or null | HTTP status of the last response; `null` when no response was received (network error, timeout before reply) |
| `redirectCount` | number | Number of HTTP redirects followed (0–2) |

A `SUMMARY` record in the default key-value store holds the run counters (`inputEntries`, `inputItems`, `skippedDuplicates`, `droppedEmptyOrNonString`, `truncatedOverLimit`, `resumedSkipped`, `processed`, `checked` = charged, `failed`, `notBilled`, `notSavedAfterLimit`, `unexpectedErrors`, `stoppedByChargeLimit`).

### Pricing

This Actor uses Apify pay per event pricing. Each run costs a small fixed start fee plus a price per domain answered by the registry; the exact amounts are shown in the Pricing tab. Only `registered` and `not-found` records are charged; `INVALID_DOMAIN`, `UNSUPPORTED_TLD`, `RATE_LIMITED`, timeouts and other errors are written with `billable: false` and cost nothing. There are no additional platform usage fees.

A domain is charged only when its record is written, and every charged record is written. When the run's "Maximum cost per run" is reached, the Actor stops and reports how many domains were left unprocessed.

### Limits and behavior

- Up to 1,000 domains per run; exact duplicates are skipped (two spellings of one domain are two records).
- IP addresses, single-label names (`localhost`) and malformed names are reported as `INVALID_DOMAIN` without any request and without charge; TLDs without an RDAP server in the IANA bootstrap (as of September 2026 this includes e.g. `.de`, `.eu`, `.io`, `.ch`, `.it`, `.es`, `.jp`) are `UNSUPPORTED_TLD` (free).
- A run interrupted by a platform migration resumes without processing or charging the same domain twice.
- **Redirects:** Up to 2 HTTP redirects (301/302/303/307/308) are followed; the third consecutive 3xx gives `TOO_MANY_REDIRECTS`. Only `https://` redirect targets are allowed. Non-`https` targets or redirects to private addresses give `BAD_REDIRECT`.
- **Response size:** RDAP responses larger than 1 MB are rejected with `Response too large`. The IANA bootstrap is limited to 2 MB.
- **Timeouts:** Every domain is subject to a single timeout period (`timeoutSecs`) covering all requests and redirects. A domain that exceeds the timeout receives `TIMEOUT`, no retries.
- **No cache:** Every domain sends a fresh HTTP request; there is no in-memory or disk cache.
- Because every error record is free, the status message and `SUMMARY` count errors under "not charged" rather than "with errors".
- **Registry without expiry:** Some registries (e.g. `.pl` by NASK) may omit `expiration` events and/or `status` codes; the record will have `expiresAt: null`, `daysToExpiry: null`, `statuses: []`, which is valid for a registered domain.

### FAQ

- **Does the Actor support IDN (internationalized domain names)?** Yes. Domain names containing non-ASCII characters are normalized to punycode (RFC 5891) before the RDAP lookup. The `domain` field in the output contains the ASCII form; the original value is preserved in `inputValue`.
- **What happens when a domain is not found in RDAP?** The RDAP server returns HTTP 404. The record is stored with `state: 'not-found'` and `billable: true`: the answer from the registry is charged.
- **Are redirects followed to a different host?** Yes. A redirect to any `https://` host is accepted, as long as it is not a private address. Only the redirect limit (2) and the overall timeout constrain the chain.
- **Does this Actor work with WHOIS (port 43)?** No. Only RDAP (HTTP-based) registries listed in the IANA bootstrap are queried.
- **Why did I get `expiresAt: null` for a registered domain?** Some TLD registries (e.g. `.pl`) do not publish expiration dates via RDAP. The domain is still `registered` and charged, but the expiry-related fields are empty.

### Other Actors by Gridline Tools

Other checks from the same publisher, each with one row per address and the same pricing rule (start fee plus price per checked item):

- [HTTP Status and Headers Checker](https://apify.com/gridlinetools/http-status-headers-checker): Audit a list of URLs in one run: status code, every hop of the redirect chain and the final URL, response time, server and content type, and six key security headers.
- [Robots.txt Checker](https://apify.com/gridlinetools/robots-txt-checker): Test a list of URLs against each site's robots.txt for a chosen user agent (RFC 9309).
- [AI Crawler Access Audit](https://apify.com/gridlinetools/ai-crawler-access-audit): See which AI and search crawlers (GPTBot, ClaudeBot, Google-Extended, CCBot and 150+ more) each site allows or blocks in robots.txt, and whether llms.txt is published.

### Disclaimer

The Actor sends plain HTTPS requests to the IANA RDAP bootstrap file and to the RDAP server of each domain's registry (no WHOIS, no port 43) and reads the public domain object. Only the **registrar entity** (name and IANA ID) is extracted from the RDAP response; registrant, administrative, technical, billing, and abuse contacts (including their names, addresses, phone numbers and email addresses) are **never** read or stored. You are responsible for complying with the registries' RDAP terms of use and rate limits. Gridline Tools does not store the domains or results beyond your own Apify account.

# Actor input Schema

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

Domain names to look up, one per line (e.g. example.com, sub.example.org, an IDN like przykład.pl, or a URL — the host name is extracted). Exact duplicates are skipped. Maximum 1,000 per run. IP addresses, single-label names (localhost) and malformed names are reported as INVALID_DOMAIN and not charged. The default list is a small self-test (Apify also runs it automatically every day to verify the Actor works).

## `timeoutSecs` (type: `integer`):

Maximum time for one domain, including redirects. On timeout the record gets error = TIMEOUT (not charged).

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

How many domains are looked up in parallel. Registries rate-limit RDAP queries — lower it if you see rate-limited records on large lists of one TLD.

## Actor input object example

```json
{
  "domains": [
    "example.com",
    "example.net"
  ],
  "timeoutSecs": 15,
  "maxConcurrency": 10
}
```

# Actor output Schema

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

Default dataset: one record per input domain.

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

SUMMARY record in the default key-value store: input, processed, checked, failed and notBilled counters, charge-limit flags.

# 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("gridlinetools/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 = {}

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gridlinetools/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/iRxE1gKnIz7h4kHvW/builds/vjw3cXiO6T3SJk2Ua/openapi.json
