# Domain Portfolio Monitor - Expiry & WHOIS Change Alerts (`technicaldost/domain-portfolio-monitor`) Actor

Watch your domains for approaching expiry, registrar transfers, nameserver changes and status flags. Uses open RDAP data, so there are no API keys and no rate-limit setup.

- **URL**: https://apify.com/technicaldost/domain-portfolio-monitor.md
- **Developed by:** [Technical Dost Solutions](https://apify.com/technicaldost) (community)
- **Categories:** Developer tools, Business
- **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 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/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

## Domain Portfolio Monitor — Expiry & WHOIS Change Alerts

Watch a list of domains and get told when something important changes: the registration is about to expire, the registrar changed, the nameservers moved, or a status flag appeared.

Built on **RDAP**, the open successor to WHOIS. No API key, no account, no rate-limit configuration.

### What it detects

| Signal | Why it matters |
|---|---|
| **Expiry approaching** | The single most common cause of accidental outages and lost domains |
| **Registrar change** | An unexpected transfer can mean a hijack |
| **Nameserver change** | DNS takeover, or a migration you were not told about |
| **Status flags** | `clientHold`, `pendingDelete`, `serverTransferProhibited` and friends |
| **DNSSEC toggled** | Signing turned on or off |
| **Expiry date moved** | Confirms a renewal actually happened |

### Typical uses

- Never lose a domain to silent expiry across a portfolio you did not personally register
- Watch client or subsidiary domains you manage but do not control
- Detect hijacks early by alerting on registrar or nameserver change
- Verify that auto-renew actually renewed
- Track competitor or acquisition-target domains

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `domains` | array | — | **Required.** Domains to watch. Full URLs and `www.` prefixes are cleaned automatically. |
| `alertDaysBeforeExpiry` | integer | `30` | Flag `expiringSoon` when expiry falls inside this window. |
| `onlyReportChanges` | boolean | `false` | Return a domain only if something changed or it is expiring soon. |
| `concurrency` | integer | `5` | Parallel lookups. |
| `stateName` | string | `domain-monitor-state` | Named store holding each domain's previous snapshot. |

```json
{
  "domains": ["github.com", "wikipedia.org", "example.co.uk"],
  "alertDaysBeforeExpiry": 30,
  "onlyReportChanges": true
}
```

### Output

```json
{
  "domain": "github.com",
  "isFirstCheck": false,
  "expiration": "2026-10-09T07:00:00Z",
  "daysUntilExpiry": 53,
  "expiringSoon": false,
  "registrar": "MarkMonitor Inc.",
  "status": ["client delete prohibited", "client transfer prohibited"],
  "nameservers": ["dns1.p08.nsone.net", "dns2.p08.nsone.net"],
  "dnssec": false,
  "changeCount": 1,
  "changes": [
    { "field": "nameservers",
      "from": ["ns1.old.net"],
      "to": ["dns1.p08.nsone.net", "dns2.p08.nsone.net"] }
  ],
  "checkedAt": "2026-08-17T11:02:44.108Z"
}
```

The first run for a domain records a baseline, so `changes` is empty and `isFirstCheck` is `true`. From the second run onward you get real diffs.

### Recommended setup

Attach an Apify Schedule and run it **daily** with `onlyReportChanges: true`. Quiet days return an empty dataset, so ongoing monitoring stays cheap, and anything that does come back is worth reading.

### Pricing

A small per-run charge covers the check, then a small charge per domain returned. With `onlyReportChanges` enabled, a day where nothing moved returns nothing and costs only the run charge.

### Notes and limitations

- A handful of TLDs do not publish RDAP yet (`.io` is a notable example). Those return an `error` record rather than failing the run. In changes-only mode a repeated identical error is reported once, not on every run, so an unsupported TLD will not quietly bill you forever.
- Nameservers and status values are normalised (lowercased, sorted, trailing dots stripped) so cosmetic registry differences are not reported as changes.
- Registrant contact details are redacted by registries under GDPR and are deliberately not returned.

# Actor input Schema

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

Domains to monitor. Full URLs and www prefixes are accepted and cleaned automatically.

## `alertDaysBeforeExpiry` (type: `integer`):

Flag a domain as expiringSoon when it expires within this many days.

## `onlyReportChanges` (type: `boolean`):

When enabled, a domain is returned only if something changed since the last run or it is expiring soon. Quiet runs then cost almost nothing.

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

How many domains to check at once.

## `stateName` (type: `string`):

Named key-value store holding the previous snapshot of each domain, used for change detection.

## Actor input object example

```json
{
  "domains": [
    "github.com",
    "wikipedia.org",
    "python.org"
  ],
  "alertDaysBeforeExpiry": 30,
  "onlyReportChanges": false,
  "concurrency": 5,
  "stateName": "domain-monitor-state"
}
```

# Actor output Schema

## `overview` (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": [
        "github.com",
        "wikipedia.org",
        "python.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("technicaldost/domain-portfolio-monitor").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": [
        "github.com",
        "wikipedia.org",
        "python.org",
    ] }

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

```

## MCP server setup

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

```

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/MS9OaBOSQCAO4tLgU/builds/8ITMbKZz23RCjmSEZ/openapi.json
