# Public DNS Subdomain Discovery Scanner (`automation-lab/public-dns-subdomain-discovery`) Actor

Discover certificate-observed subdomains of authorized root domains, check public DNS A/AAAA/CNAME records, and export hostnames with issuance provenance for asset inventories.

- **URL**: https://apify.com/automation-lab/public-dns-subdomain-discovery.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.34 / 1,000 item processeds

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

## Public DNS Subdomain Discovery Scanner

Find concrete public subdomains of authorized root domains from certificate-transparency issuances, then check their current A, AAAA and CNAME DNS records. This subdomain scanner produces one structured row per hostname, with certificate and lookup provenance for recurring asset inventories.

### What does it do?

Supply one to ten root domains. The Actor reads recent public Cert Spotter issuance records, discards wildcard names (a wildcard is not evidence a particular hostname exists), deduplicates concrete child names, resolves DNS, and exports the default dataset. It does not brute-force names, probe websites, detect takeovers, or claim to enumerate every existing host.

### Who is it for?

Security and IT teams can periodically snapshot certificate-observed assets they own. Domain administrators can review newly issued names and public resolution state. Analysts can join exported rows with their own authorized inventories.

### Why use it?

Certificate evidence and current DNS evidence are separate: a name may occur on a certificate but no longer resolve. Each row retains issuance ID, issuer, validity dates and a provenance URL alongside DNS answers, rather than conflating historical visibility with live service availability. Wildcards and root-only names are excluded.

### Getting started

1. Enter root domains you own or are authorized to inventory, such as `python.org` for a public demonstration.
2. Start with `maxItems: 5` and `maxPagesPerDomain: 1` to inspect a small sample.
3. Run the Actor and open the **Hostnames** dataset view.
4. Export JSON or CSV and compare subsequent *scheduled* run datasets in your own pipeline. The Actor does not calculate deltas or send alerts.

### Input fields

| Field | Meaning | Limits |
| --- | --- | --- |
| `domains` | Plain DNS root names, not URLs, wildcard names or IPs | 1–10 |
| `maxItems` | Maximum distinct concrete child names in the entire run | 1–1,000; default 100 |
| `maxPagesPerDomain` | Recent issuance pages from Cert Spotter per root | 1–3; default 1 |

```json
{"domains":["python.org"],"maxItems":5,"maxPagesPerDomain":1}
```

### Output fields

The default dataset contains `rootDomain`, `hostname`, `dnsStatus` (`resolved` or `no_records`), `aRecords`, `aaaaRecords`, `cnameRecords`, `certificateId`, `certificateNotBefore`, `certificateNotAfter`, `issuer`, `provenanceUrl`, and `checkedAt`. DNS record arrays can be empty. Issuance validity and issuer may be null when absent upstream. An observed CNAME alone counts as resolved even if its ultimate destination is currently unavailable; this is a DNS record check, not a website availability test.

### Example output

A local Python.org test observed `www.python.org`, status `resolved`, A and AAAA addresses, CNAME `dualstack.python.map.fastly.net`, certificate ID `13425610576`, issuer `GlobalSign`, and the corresponding Cert Spotter issuance URL. Addresses and certificates can change between runs; do not treat the example as a fixed result.

### How much does it cost to discover public subdomains?

The Actor uses a one-time `start` event and an `item` event only for emitted hostname rows. At the BRONZE spend tier, a run costs $0.01 to start and $0.00056 per emitted hostname. Five results cost an estimated $0.01280; 25 cost $0.02400; 100 cost $0.06600. FREE is $0.000644 per hostname; SILVER is $0.0004368; GOLD, PLATINUM and DIAMOND are $0.000336 each. Spend tiers depend on your monthly Apify Store spend, not the number of results in one run; check the live Apify pricing panel for your effective tier. An empty but successful query has no item charge, but the start event still applies. Platform usage and payout estimates may change with corrections, refunds, fraud, disputes, taxes and clawbacks.

### Scope and coverage limits

This is CT-derived discovery, not a complete zone transfer, passive-DNS database, historical archive, DNS brute force or takeover audit. Cert Spotter's free endpoint is rate-limited and may return only recent issuances. `maxPagesPerDomain` caps history; `maxItems` caps output, with capacity reserved for each remaining root (a sparse root may leave the run below the cap). A certificate can name a hostname that no longer resolves, and one hostname can have several certificates; the first observed issuance is recorded. No wildcard expansion takes place. DNS checks use the run's public resolver and may differ from private split-horizon DNS.

### Failure and retry behavior

A malformed root is rejected. Transient CT 429/5xx and network errors retry twice with bounded delays; persistent failures stop the run rather than silently returning an incomplete inventory. DNS resolver errors other than ordinary no-data/no-domain fail rather than being mislabeled `no_records`. For large or rate-limited domains, lower page count and schedule less frequently; repeated immediate reruns cannot bypass upstream quotas.

### Integrations

Use an Apify schedule to refresh your authorized inventory. Export the default dataset to JSON/CSV, Google Sheets, a SIEM or an asset database. Compare `hostname` keyed rows across run datasets in your own automation to detect differences; this Actor emits snapshots, not change events. Pair it with [Bulk DNS AAAA Record Checker](https://apify.com/automation-lab/bulk-dns-aaaa-record-checker) when you already have explicit hostnames and only need IPv6 checks.

### Run through the API

Use your own Apify token; do not put it in publicly shared Task inputs.

```bash
curl -X POST 'https://api.apify.com/v2/acts/automation-lab~public-dns-subdomain-discovery/runs?token=YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"domains":["python.org"],"maxItems":5}'
```

The returned run's `defaultDatasetId` identifies the JSON/CSV output. For synchronous runs, use the Apify run-sync API only if the expected runtime fits your timeout.

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/public-dns-subdomain-discovery').call({ domains: ['python.org'], maxItems: 5 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

```python
from apify_client import ApifyClient
import os
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/public-dns-subdomain-discovery').call(run_input={'domains': ['python.org'], 'maxItems': 5})
rows = client.dataset(run['defaultDatasetId']).list_items().items
print(rows)
```

### MCP integration

Expose this Actor as a tool for an agent with the hosted Apify MCP server:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/public-dns-subdomain-discovery"
```

For **Claude Desktop**, **Cursor**, or **VS Code**, configure the hosted HTTP server in the client's MCP settings (use the client's current HTTP transport syntax):

```json
{"mcpServers":{"apify":{"url":"https://mcp.apify.com?tools=automation-lab/public-dns-subdomain-discovery"}}}
```

Example prompts: “Scan certificate-observed subdomains of python.org, limit to five, and identify those with no public A/AAAA/CNAME records.” “Export the public DNS inventory for github.com with certificate issuance provenance.” Supply your own authorization for any domain you inventory; the Actor does not check ownership.

### Data handling and support

No AI provider or model is used. The Actor sends each requested root to Cert Spotter's anonymous public API and each discovered child hostname to the run's DNS resolver. Cert Spotter may log root queries under its own policies; we cannot control that retention. No separate Actor-side cache, cookie store or account is kept. Inputs, output datasets and logs remain in Apify run storage under your account's configured retention; delete them from your Apify storage when no longer needed. For product or run problems, use the Actor's Apify Store issue channel with the run ID and sanitized input; never include tokens or confidential asset names in a public report.

### Legality and responsible use

Only inventory domains you own or have explicit permission to assess. Public CT and DNS records can contain sensitive-looking names even when publicly disclosed; control exports, avoid attaching credentials, and respect upstream rate limits. This is observational metadata, not a vulnerability assessment or authorization to probe a host.

### FAQ

**Does `no_records` mean a service is offline?** No. It only means no public A, AAAA or CNAME answer was returned by the resolver at check time. Other DNS types, private DNS and service health are outside scope.

**Why are older hostnames missing?** The API lists issuances by pages; increase `maxPagesPerDomain` up to three, but do not assume complete CT coverage.

**Why did the Actor fail on an apparently valid domain?** Enter a plain root without scheme/path/wildcard. CT API rate limits or DNS resolver transient errors are visible in the run log; wait for the source quota to recover instead of hammering it.

### Related automation-lab Actors

[theHarvester Domain OSINT Collector](https://apify.com/automation-lab/theharvester-domain-osint-collector) serves broader authorized domain intelligence, while [Bulk Domain Security Posture Checker](https://apify.com/automation-lab/bulk-domain-security-posture-checker) audits security configuration of supplied domains. Neither is the same as this CT-derived child-hostname snapshot.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/public-dns-subdomain-discovery/changelog.md

# Actor input Schema

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

One to ten root domains you own or have permission to inventory; no URLs or wildcards.

## `maxItems` (type: `integer`):

Stop after this many distinct concrete subdomains across all roots.

## `maxPagesPerDomain` (type: `integer`):

Number of newest Cert Spotter issuance pages to inspect per root (up to three). Older certificates may be omitted.

## Actor input object example

```json
{
  "domains": [
    "python.org"
  ],
  "maxItems": 5,
  "maxPagesPerDomain": 1
}
```

# Actor output Schema

## `dataset` (type: `string`):

Default dataset of discovered concrete hostnames.

# 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": [
        "python.org"
    ],
    "maxItems": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/public-dns-subdomain-discovery").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": ["python.org"],
    "maxItems": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/public-dns-subdomain-discovery").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": [
    "python.org"
  ],
  "maxItems": 5
}' |
apify call automation-lab/public-dns-subdomain-discovery --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/public-dns-subdomain-discovery"
        }
    }
}
```

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/iQTqdA7Mbf0FgMFLG/builds/E6cooQ7OtQh3ZPZ3h/openapi.json
