# Domain Inspector - DNS, WHOIS, TLS and Tech Stack (`dami_studio/domain-inspector`) Actor

Check up to 500 domains a run, one row each: DNS records, the registrar and expiry date from RDAP (the registry-run successor to WHOIS), SSL certificate dates, redirects, security headers, robots.txt, sitemap and the tech the site runs. $1.50 per 1,000 domains.

- **URL**: https://apify.com/dami_studio/domain-inspector.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Developer tools, SEO tools, Automation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.44 / 1,000 domains

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

**Check a list of domains and get one row each with the DNS records, the registrar and expiry date, the SSL certificate and what the site runs**: plus the redirect chain, the security headers, robots.txt, sitemap.xml and a short list of technology signals.

Registration data comes from RDAP, the registry-run replacement for the old WHOIS service. Not every registry publishes it, so on some country domains the registrar and the expiry date come back empty while the rest of the row is filled in. And rows are written only once every domain in the run has finished.

| | |
|---|---|
| **Input** | Domains or URLs, up to 500 per run |
| **Output** | One row per domain: DNS records, registrar and dates, certificate and expiry, redirects, security headers, robots.txt, sitemap, technology signals |
| **Ceiling** | 500 domains per run |
| **Account needed** | None, and no API key |
| **Price** | $1.50 per 1,000 domains on the Free plan, a little less on paid plans. The free plan's $5 a month covers about 3,300 |

### 🔍 What Domain Inspector does

Each domain gets four independent checks, and you can switch any of them off.

**DNS** resolves six record types, reporting per-record errors rather than collapsing them into one failure. **RDAP** asks the registry for the registration record. **Certificates** connect on port 443 and read the certificate, including whether Node's trust store accepts the chain. **Web** requests both `http://` and `https://`, follows the redirects, and reads the final status, the title, the canonical URL, the Open Graph block, the security headers and the technology signals.

If robots.txt and sitemap.xml checking is on, both are fetched from whichever origin answered best, and any sitemap URLs declared in robots.txt come back with them.

Anything that went wrong without killing the row lands in `warnings`, with `warningsCount` beside it, so you can sort by how messy a domain is.

### 📋 What data you get from each domain

| What you get | Field |
|---|---|
| A, AAAA, MX, NS, TXT and CAA records | `dns`, plus `dnsA` and `dnsAAAA` up top |
| Registrar, statuses, nameservers, registration, update and expiry dates | `rdap`, plus `registrationExpiresAt` up top |
| The certificate: issuer, subject, alternative names, dates, days left, trust verdict | `tls`, plus `tlsExpiresAt` up top |
| Status, redirect chain, final URL, title, metadata and headers, per scheme | `http`, `https`, plus `httpStatus`, `httpsStatus` and `title` up top |
| Security headers | `https.securityHeaders`, `http.securityHeaders` |
| Technology signals | `technologies` |
| robots.txt and sitemap.xml, with any sitemaps robots.txt declares | `robots`, `sitemap` |
| What went wrong without stopping the row | `warnings`, `warningsCount` |

### ▶️ How to check DNS, registration and SSL certificates for a list of domains

1. Open [Domain Inspector](https://apify.com/dami_studio/domain-inspector) and click **Try for free**.
2. Paste your domains into **Domains or URLs**, one per line.
3. Leave the four check boxes on for a full row, or switch off the ones you do not need.
4. Click **Start**. Rows land once every domain has finished, so a long list stays quiet for a while.
5. Download the dataset as JSON, CSV or Excel, or read it from the Apify API.

Start it with an empty list and you get one labelled sample row. It is not charged.

### 💰 How much does it cost to inspect domains?

**$1.50 per 1,000 domains** on the Free plan. Paid plans pay a little less: $1.48 on Starter, $1.46 on Scale and $1.44 on Business. The free plan's $5 a month covers about 3,300 domains. Switching checks off does not change the price.

You pay per domain that came back with something. A parked domain counts, because it still has DNS and registration data. A domain where every check came back empty does not, and neither does the sample row or an invalid entry.

If you set a maximum charge for a run, it stops at the last domain that fits, and the run's status says so.

### 📥 What you give it

```json
{
  "domains": ["github.com", "example.org", "https://news.ycombinator.com/"],
  "maxConcurrency": 5,
  "requestTimeoutSecs": 8,
  "checkRdap": true,
  "checkTls": true
}
```

| Field | Default | What it is |
|---|---|---|
| `domains` | none; the box starts at `github.com` | Domains or full URLs, one per line. A URL is reduced to its hostname. Up to 500. |
| `domain` | none | A single domain, added to the list above. Handy for a task or an API call with one target. |
| `maxConcurrency` | `5` | How many domains are inspected at once, 1 to 20. |
| `requestTimeoutSecs` | `8` | The limit on each individual lookup, 3 to 20. There is a deadline on the whole domain as well. |
| `maxRedirects` | `5` | How many hops to follow, up to 10. Every hop is safety checked. |
| `checkRdap` | `true` | Registration data from the registry. |
| `checkWeb` | `true` | The HTTP and HTTPS requests, the redirect chain, headers and page metadata. |
| `checkTls` | `true` | The certificate on port 443. |
| `checkRobotsAndSitemap` | `true` | robots.txt and sitemap.xml. |
| `proxyConfiguration` | off | Optional network settings for the web requests only. DNS, RDAP and the certificate check always connect directly. |

**`example.com` and `www.example.com` are two different domains here.** Both get inspected, both get a row, and both count against your total. Deduplication only catches exact repeats.

**The 500 limit is applied before duplicates are removed**, and the single `domain` field is added at the end of the list. So a list of 500 with repeats in it can push your single domain out.

**Setting `maxRedirects` to 0 does not switch redirects off.** It falls back to 5.

### 📤 What you get back

The flat summary fields from a real row of 14 September 2026, with the nested blocks left out here because a full row runs to several thousand characters:

```json
{
  "ok": true,
  "domain": "github.com",
  "inspectedAt": "2026-09-14T05:42:44.663Z",
  "httpsStatus": 200,
  "httpStatus": 200,
  "title": "GitHub · Change is constant. GitHub keeps you ahead. · GitHub",
  "dnsA": ["140.82.114.3"],
  "dnsAAAA": [],
  "tlsExpiresAt": "2026-11-29T23:59:59.000Z",
  "registrationExpiresAt": "2028-10-09T18:20:50.000Z",
  "technologies": ["React", "Server: github.com"],
  "warningsCount": 0
}
```

Under those sit the full blocks. From the same row, the certificate one:

```json
"tls": {
  "available": true,
  "authorized": true,
  "authorizationError": null,
  "protocol": "TLSv1.3",
  "subject": { "CN": "github.com" },
  "issuer": { "C": "GB", "O": "Sectigo Limited", "CN": "Sectigo Public Server Authentication CA DV E36" },
  "subjectAlternativeNames": ["github.com", "www.github.com"],
  "validFrom": "2026-09-01T00:00:00.000Z",
  "expiresAt": "2026-11-29T23:59:59.000Z",
  "daysUntilExpiry": 76,
  "serialNumber": "A59EBDB596751DB7F5C095079613953C",
  "fingerprint256": "46:B6:01:EE:08:B4:18:CF:8A:3A:1E:..."
}
```

| Block | What is in it |
|---|---|
| `dns` | `a`, `aaaa`, `mx`, `ns`, `txt`, `caa`, plus `errors` keyed by record type and `hasData`. |
| `rdap` | `registrar`, `statuses`, `nameservers`, `registeredAt`, `updatedAt`, `expiresAt`, `handle`. `available: false` when the registry publishes nothing. |
| `tls` | The certificate, as above. `authorized` is Node's own trust verdict, and `authorizationError` says why when it is false. |
| `http`, `https` | Separate blocks per scheme: `status`, `finalUrl`, `redirects`, `redirectCount`, `metadata`, `responseHeaders`, `securityHeaders`, `technologies`. |
| `robots`, `sitemap` | `present`, `status`, `finalUrl`, and any `sitemaps` declared in robots.txt. |
| `warnings` | Plain sentences naming anything that went wrong but did not stop the row. |

The overview table in the Console shows the flat summary only. Switch to JSON or All fields for the nested blocks.

### 🧾 Reading the output

Three kinds of row can land in your dataset.

| Row | How to spot it | Charged |
|---|---|---|
| An inspected domain | `ok: true` and a `domain` | yes |
| The sample row | `_sample: true` | no |
| A diagnostic | `ok: false` and an `errorCode` | no |

**Filter on `_sample` being absent, not on `ok`.** The sample row also carries `ok: true`.

| Code | What it means |
|---|---|
| `BAD_INPUT` | The entry was not a domain: a bare IP address, a single word with no dot, or something that would not parse. The `input` field on the row shows what you sent. |
| `INSPECTION_FAILED` | Every check came back empty. Usually an unregistered or misspelled domain. |
| `TIME_LIMIT` | The run got close to its time limit and stopped on its own. The domains delivered before that are complete and charged; the ones it had not finished have no row and no charge. Give the run more time, or send fewer domains, to get the rest. The run ends as failed only when this stopped it before any domain came back. |

A `BAD_INPUT` row has no `domain` field, only `input`, so it shows in the overview table as a line with the domain column blank.

### 💡 What people use it for

- Watching certificate and registration expiry across a portfolio, on a schedule, sorted by `daysUntilExpiry`.
- Auditing security headers across the domains a company owns, in one pass.
- Checking a list of suppliers or acquisition targets before a first email: is the site live, who registered it, what is it built with.
- Confirming a migration actually landed, by reading the redirect chain and the final URL.

### 🚧 What it does not do

- **No JavaScript.** The title, metadata and technology signals come from the HTML the server sent, so a site that renders everything client side looks emptier than it is.
- **Technology detection is a short list of signals**, not a full fingerprinting suite. Treat `technologies` as a hint.
- **RDAP only.** There is no fallback to the old WHOIS service, so registries that publish no RDAP come back with `available: false`.
- **500 domains per run**, counted before duplicates are removed.
- **Nothing is delivered until every domain has finished.** A run that is aborted partway through writes no rows at all, so split very large lists.
- **A domain pointing at a private address gets its web and certificate checks skipped**, with a warning saying so. DNS and registration still come back.
- **One certificate, on port 443.** No other ports, no chain download, no revocation check.
- **No subdomain discovery.** It inspects exactly what you give it.
- **No traffic, ranking or backlink figures.**
- It is not an official tool of any platform or company named on this page. Dami's Studio is independent and is not affiliated with or endorsed by any of them.

### 🧭 Which site checker do you need?

| If you want | Use |
|---|---|
| DNS, registration, certificates and headers for a list of domains | This one |
| To crawl a site and read what is on its pages | [Website Intelligence Crawler](https://apify.com/dami_studio/website-intelligence-crawler) |
| A picture of the page rather than its headers | [Website Screenshot Generator](https://apify.com/dami_studio/website-screenshot-generator) |

### ❓ Questions people ask

#### Do I need an API key to look up DNS and WHOIS data?

No. DNS, RDAP and certificates are all public lookups.

#### Why is the registrar empty on some domains?

That registry does not publish RDAP. There is no second source here, so the field stays empty rather than being guessed.

#### Can I check subdomains?

Only ones you list yourself. It does not go looking for them.

#### How long do 500 domains take?

It depends on **Concurrent domains** and how fast the targets answer; each domain has its own deadline, so one slow site cannot stall the run. Raise concurrency carefully.

#### Why did I get nothing back from a run I stopped early?

Rows are written once every domain has been inspected. Stop it before then and nothing is written.

#### Can I call it from code or connect it to an AI assistant?

Yes. The [API tab](https://apify.com/dami_studio/domain-inspector/api/python) has ready-made code for Python, JavaScript and the command line. For Claude, ChatGPT or another MCP client, connect `https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/domain-inspector`. Either way the run happens on your Apify account at the same price.

#### Is it legal to check domains like this?

DNS, RDAP and certificates are published for public lookup, and robots.txt and sitemap.xml are files sites publish deliberately. Apify's write-up on [the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/) is a good starting point, and we are not lawyers.

### 🆘 If something breaks

Open the **Issues** tab on the actor page. Send the run ID and the domain that behaved oddly. The `warnings` array on the row usually explains it, and `errorCode` names it when the whole row failed.

# Actor input Schema

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

Domains or public HTTP(S) URLs to inspect, one per line. URLs are reduced to their hostname. Duplicates are removed. Maximum 500.

## `domain` (type: `string`):

Optional single-domain shortcut. It is combined with the domains list.

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

Number of domains inspected at once.

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

Timeout for each DNS, RDAP, HTTP, or TLS operation. A hard per-domain deadline is also applied.

## `maxRedirects` (type: `integer`):

Maximum redirects followed separately for HTTP, HTTPS, robots.txt, and sitemap.xml. Every redirect target is safety-checked.

## `checkRdap` (type: `boolean`):

Look up public registration dates, status, registrar, nameservers, and handle where the registry provides RDAP data.

## `checkWeb` (type: `boolean`):

Inspect both HTTP and HTTPS status, redirect chains, final URL, title, metadata, security headers, and basic technology signals.

## `checkTls` (type: `boolean`):

Inspect the certificate on port 443, including validity dates, issuer, subject, SANs, fingerprint, and Node.js trust result.

## `checkRobotsAndSitemap` (type: `boolean`):

Check standard robots.txt and sitemap.xml locations on the best working web origin.

## `proxyConfiguration` (type: `object`):

Optional. Off by default because public domain checks normally need no proxy. When enabled, it is used only for HTTP(S), robots.txt, and sitemap.xml. DNS, RDAP, and TLS always connect directly.

## Actor input object example

```json
{
  "domains": [
    "example.com",
    "https://apify.com/"
  ],
  "domain": "example.com",
  "maxConcurrency": 5,
  "requestTimeoutSecs": 8,
  "maxRedirects": 5,
  "checkRdap": true,
  "checkWeb": true,
  "checkTls": true,
  "checkRobotsAndSitemap": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One consolidated dataset row per normalized domain. Successful rows include all available checks and warnings. Invalid or totally unreachable domains return uncharged diagnostic rows.

# 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"
    ]
};

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

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

```

## MCP server setup

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

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/jvjdea3ItfAC1hYI5/builds/W5TDhWAapzOfBzgiR/openapi.json
