# Domain Availability Checker: Expiry & Drop Date (`aiqlabs/domain-availability-checker`) Actor

A bulk domain expiry checker and availability checker over RDAP. Free, registered or reserved; where the name sits in the delete lifecycle (grace, redemption, pendingDelete); when it could drop; whether it can be transferred today; and what every EPP status means.

- **URL**: https://apify.com/aiqlabs/domain-availability-checker.md
- **Developed by:** [Ai-Q Labs](https://apify.com/aiqlabs) (community)
- **Categories:** SEO tools, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 results

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/platform/actors/running/actors-in-store#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

## Domain Availability Checker: Expiry & Drop Date

A bulk **domain expiry checker** and availability checker built on RDAP. Paste a
list of domains — or a list of bare names plus the TLDs to try — and get **one row
per domain**.

Most availability checkers answer one question: taken, or free. That leaves the
three questions people actually act on unanswered:

1. **If it's taken, is it on its way out — and when could I register it?**
2. **Can it be moved to another registrar today, or is something blocking that?**
3. **What do all those status codes on the WHOIS record mean?**

This Actor answers all three, and refuses to answer the first one when it cannot
do so honestly.

### The delete lifecycle, not just a yes/no

A domain does not go from *registered* to *free*. For gTLDs it walks a fixed
path, and where it currently stands tells you exactly what is possible:

| Stage | What it means | Can you register it? |
|---|---|---|
| `active` | Normal service | No |
| `auto_renew_grace` | Expired, in the registrar's grace window (up to 45 days) | No — the owner can still renew at the normal price |
| `redemption` | Deleted, in the 30-day Redemption Grace Period | No — **only the owner** can restore it, for a large fee |
| `pending_delete` | The final **5 days** before release | **Not yet — but this is the window to watch** |
| `on_hold` | Suspended in DNS by the registrar or registry | No |
| `no_nameservers` | Registered but not delegated anywhere | No |
| `reserved` | Held back by the registry | No |
| `available` | Not registered | Yes |

Every row carries the window: `dropWindowEarliest`, `dropWindowLatest`,
`dropWindowConfidence` and — the field that matters — **`dropWindowBasis`, the
reasoning in one sentence**.

```
dropping-name.com   pending_delete   high     pendingDelete lasts 5 days for gTLDs,
                                              counted from the last registry change (2026-07-28).
lapsed-name.com     redemption       medium   Redemption started around 2026-07-25: 30 days of
                                              redemption plus 5 days of pendingDelete.
someones-brand.com  active           low      Only reachable if the owner lets it expire on
                                              2027-03-04 and never renews, which is the
                                              exception rather than the rule.
```

Confidence is `high` only inside pendingDelete, where the remaining window is a
fixed five days. For a domain still in service it is `low`, and the basis says
why — because the overwhelmingly likely outcome is that the owner renews. **No
single date is ever presented as a fact when it is an inference.**

### It will not tell you a domain is free unless it knows

This is the failure mode that costs money: a checker reports **available**, you
build a plan around the name, and it was never free at all.

Two guards, both on by default:

**1. The TLD must actually have an RDAP service.** RDAP signals "not registered"
with HTTP 404 — and so does a base URL for a TLD that never had RDAP, or quietly
stopped. Those are indistinguishable from the response alone. So the run starts by
loading [IANA's official RDAP service list](https://data.iana.org/rdap/dns.json)
and checks the TLD is on it. If it isn't, the verdict is **`unknown` with
`rdap_not_supported`**, never `available`.

That is not a theoretical case. In a live run today, of nine TLDs probed,
**`.mil`, `.edu`, `.cu` and `.bd` had no IANA-listed RDAP service** — every name
under them would 404, and a checker that trusted the 404 would call the entire
namespace free.

**2. DNS gets a veto.** A name that resolves, has name servers, or accepts mail is
registered whatever RDAP said. When the two disagree the row comes back `unknown`
with `dns_contradicts_rdap` and says which signal contradicted which.

If the IANA list itself cannot be fetched, **every** verdict in that run is
downgraded to `unknown` rather than guessed.

### Transfer readiness

Two independent brakes stop a registrar transfer, and both are reported:

- **Lock statuses** — `clientTransferProhibited` (the owner can remove this) and
  `serverTransferProhibited` (the registry set it).
- **ICANN's 60-day rule** — no transfer within 60 days of registration or of a
  previous transfer. Computed from the RDAP event dates, so you get
  `transferBlockedUntil` as a date rather than a rule to remember.

`transferable` is a plain boolean; `transferBlockers` explains each reason in a
sentence. An ordinary transfer lock is **not** reported as a problem — nearly
every well-run domain carries one — so a clean row stays clean.

### Every status explained

RDAP hands back status codes and expects you to know them. `statusMeanings` gives
each one a sentence:

```
redemption period: Deleted and in the 30-day Redemption Grace Period. Only the current
                   owner can restore it, for a large fee. It cannot be registered by
                   anyone else yet.
server hold:       The registry itself has suspended the domain in DNS. Nothing resolves.
add period:        Registered within the last 5 days. The registrar can still delete it
                   for a refund.
```

Both spellings are handled — RDAP's spaced form (`client transfer prohibited`) and
the raw EPP camelCase (`clientTransferProhibited`) that many servers return.

### Input

```json
{
  "domains": ["apify.com", "example.com", "myidea"],
  "tlds": ["com", "net", "io"],
  "crossCheckDns": true,
  "onlyAvailable": false
}
```

Three input shapes, because all three are how people arrive at this problem:

- **A full domain** — `example.com`, used as written
- **A URL** — `https://www.example.com/page`, reduced to the host
- **A bare name** — `myidea`, expanded across `tlds` into `myidea.com`,
  `myidea.net`, `myidea.io`

Internationalised names are converted to punycode, so `日本.jp` works. Subdomains
climb to the registrable name: `www.example.co.uk` is answered with
`example.co.uk`, multi-level suffix and all.

A bare name with **no** `tlds` list is reported as skipped, not guessed at.

| Option | Default | What it does |
|---|---|---|
| `crossCheckDns` | `true` | Let DNS veto a wrong "available". |
| `onlyAvailable` | `false` | Keep only free names — for screening a long candidate list. |
| `onlyIssues` | `false` | Keep only warnings and errors: expiring, expired, suspended, in redemption, unverifiable. |
| `maxConcurrency` | `8` | Lookups in flight. Registry RDAP servers rate-limit; raising this on a long list produces errors, not speed. |
| `requestTimeoutSecs` | `20` | Per-request timeout. |
| `dnsServers` | `[]` | Resolve against specific servers, e.g. `1.1.1.1`. |
| `maxDomains` | `20000` | Cap per run, applied after TLD expansion. |

### Output

| Field | Meaning |
|---|---|
| `verdict` / `verdictBasis` | `available`, `registered`, `reserved` or `unknown` — and the evidence behind it. |
| `severity` / `issues` / `notes` | `ok`/`info`/`warning`/`error`, the codes, and one sentence each. |
| `lifecycleStage` / `lifecycleMeaning` | Where the name sits in the delete path. |
| `dropWindow*` | Earliest, latest, confidence and the reasoning. |
| `expiresAt` / `daysUntilExpiry` / `registeredAt` / `domainAgeDays` / `lastChangedAt` / `transferredAt` | The dates. |
| `registrar` / `registrarIanaId` / `registrantOrg` / `abuseEmail` | Who holds it and who to contact about abuse. |
| `transferable` / `transferLocked` / `transferBlockedUntil` / `transferBlockers` | Transfer readiness. |
| `statuses` / `statusMeanings` | Raw codes, and what each one means. |
| `nameservers` / `dnssec` / `dnsResolves` / `dnsHasMx` | Delegation and DNS reality. |
| `rdapServer` / `rdapQueried` / `rdapError` | Which server answered which question. |

Issue codes: `available`, `expired`, `expires_within_7_days`,
`expires_within_30_days`, `in_redemption`, `pending_delete`, `on_hold`,
`no_nameservers`, `no_expiry_published`, `transfer_blocked_60_day_rule`,
`rdap_not_supported`, `dns_contradicts_rdap`, `bootstrap_unavailable`,
`rdap_error`.

A `SUMMARY` record in the key-value store holds the totals plus
**`droppingSoonest`** — the names with a real drop window, sorted by date.

Dataset views: **Overview**, **Drop watch**, **Transfer readiness**, **Statuses
explained**.

### How it behaves

- **RDAP over HTTPS, and DNS. Nothing is scraped**, no HTML is fetched, no page is
  rendered. Queries go to the registry servers IANA publishes for that TLD.
- **One RDAP request per domain** in the normal case, against the registry's own
  server. The IANA service list is fetched once per run and cached.
- **A subdomain costs one extra request** as it climbs to the registrable name.
- Concurrency defaults to a deliberately modest 8, because registry RDAP servers
  rate-limit.
- An honest user agent that identifies the Actor and links back to this page.

### Limits, stated plainly

- **The drop windows are gTLD arithmetic.** The 45/30/5-day chain is ICANN policy
  for gTLDs; ccTLDs set their own rules and some have no redemption period at all.
  Read `dropWindowBasis` before acting on a date.
- **Registrars differ on grace periods.** The auto-renew window is up to 45 days
  but many registrars use less, which is why the window has two ends.
- **Not every registry publishes everything.** Some omit the expiry date, some omit
  the registrant, some omit the entry date for a status. Where a fact is missing
  the field is `null` and, where it changes the answer, an issue says so —
  `no_expiry_published`, or a `dropWindowBasis` that admits the entry date was not
  published.
- **`available` means "free according to the registry and absent from DNS"**, not
  "cheap" and not "unclaimed as a trademark". Premium and registry-reserved names
  can still be refused at checkout.
- Registry RDAP servers rate-limit. A long list at high concurrency will produce
  `rdap_error` rows rather than wrong answers.

***

Built by **Ai-Q Labs**. Verified by 49 unit tests and 40 live checks against the
real IANA bootstrap and real registry servers before publishing.

# Actor input Schema

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

One per line. Full domains (example.com), URLs (https://example.com/page), or bare names with no TLD (myidea) which are expanded across the TLD list below.

## `tlds` (type: `array`):

Only used for entries with no dot. "myidea" plus com, net, io becomes three checks. Ignored for entries that already carry a TLD.

## `crossCheckDns` (type: `boolean`):

Also resolve the name. A domain that is live in DNS is registered whatever RDAP said, so this catches a wrong "available" verdict before you act on it.

## `onlyAvailable` (type: `boolean`):

Keep rows whose verdict is available and drop the rest. Useful when screening a long list of candidate names.

## `onlyIssues` (type: `boolean`):

Keep rows whose severity is warning or error - expiring, expired, suspended, in redemption, or unverifiable.

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

How many domains to look up at the same time. Registry RDAP servers rate-limit, so raising this on a long list can produce errors rather than speed.

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

Give up on a single RDAP request after this many seconds.

## `dnsServers` (type: `array`):

Optional. Resolve against these instead of the container default, for example 1.1.1.1 or 8.8.8.8.

## `maxDomains` (type: `integer`):

Safety cap on how many domains one run will check, applied after TLD expansion.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "example.com",
    "this-name-is-almost-certainly-free-71.com"
  ],
  "tlds": [],
  "crossCheckDns": true,
  "onlyAvailable": false,
  "onlyIssues": false,
  "maxConcurrency": 8,
  "requestTimeoutSecs": 20,
  "dnsServers": [],
  "maxDomains": 20000
}
```

# Actor output Schema

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

No description

## `csv` (type: `string`):

No description

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

No description

# 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",
        "example.com",
        "this-name-is-almost-certainly-free-71.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("aiqlabs/domain-availability-checker").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",
        "example.com",
        "this-name-is-almost-certainly-free-71.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("aiqlabs/domain-availability-checker").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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",
    "example.com",
    "this-name-is-almost-certainly-free-71.com"
  ]
}' |
apify call aiqlabs/domain-availability-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=aiqlabs/domain-availability-checker",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/415BNzlhaizdW5BAj/builds/DlP6CJP0MeQWyi1wn/openapi.json
