# Subdomain Finder: Certificate Transparency (`glitchbound/subdomain-scraper`) Actor

Enumerate every subdomain of a domain from public Certificate Transparency logs. No brute-force wordlist and no DNS hammering, because every TLS certificate is logged, so the hosts are already public record. Deduplicated to one row per hostname, with first seen, issuing CA and current validity.

- **URL**: https://apify.com/glitchbound/subdomain-scraper.md
- **Developed by:** [Daniel Meshulam](https://apify.com/glitchbound) (community)
- **Categories:** Developer tools, SEO tools, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.25 / 1,000 hostnames

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/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

## Subdomain Finder: Certificate Transparency

Every subdomain of a domain, from public Certificate Transparency logs.

```json
{ "domains": ["apify.com"] }
```

### Why CT logs beat a wordlist

Most subdomain tools brute-force a dictionary against DNS: thousands of queries,
noisy, and it finds only names someone thought to put in the list.

Certificate Transparency is the opposite. Since 2018 every certificate a public
CA issues **must** be published to append-only logs, or browsers reject it. So
any host that has ever been served over HTTPS is already public record,
including the ones nobody would guess: `jenkins-old.`, `vpn-test.`,
`internal-api-staging.`

No brute force. No DNS hammering. Nothing that looks like an attack in someone's
logs.

### Deduplication is the whole job

Raw CT output for one mid-size domain is routinely **ten times** more rows than
there are real hosts. A single certificate carries many SANs, every 90-day
renewal republishes them all, and wildcards arrive as `*.example.com`.

A raw query against `apify.com` returns around 958 certificate records. This
returns the distinct hostnames, and you are billed for those, not for the
duplicates.

Each row aggregates every certificate seen for that host:

| Field | |
|---|---|
| `hostname` | the subdomain |
| `depth` | label count, sort by it to see structure |
| `firstSeen` | earliest `notBefore` across all its certificates |
| `lastSeen` | latest `notAfter`, when coverage runs out |
| `certificateActive` | `true` if a certificate is valid **right now** |
| `certificates` | how many certificates named this host |
| `issuers` | which CAs issued them |
| `isWildcard` | `*.` entry rather than a concrete host |

### Live hosts vs. history

By default you get hosts with unexpired certificates, the current attack
surface.

```json
{ "domains": ["example.com"], "includeExpired": true }
```

Turn on `includeExpired` and you also get everything that *used* to exist.
Decommissioned staging servers and forgotten legacy hosts show up here, and in a
security review that is usually the interesting half.

### Use cases

- **Attack-surface mapping**: the full external footprint of a domain you are authorised to assess
- **Asset inventory**: find hosts that never made it into the CMDB
- **Shadow IT discovery**: teams that stood up their own subdomain
- **M\&A diligence**: what does the target actually run
- **Certificate expiry**: sort by `lastSeen`, find coverage about to lapse
- **Bug bounty recon**: within the program's declared scope

### Pricing

Charged per unique hostname returned, not per certificate record. A domain with
no certificates in CT produces an `error` row and costs **$0.00**.

### Subdomain enumeration without touching the target

Every TLS certificate a public CA issues is published to append-only
**Certificate Transparency** logs. That makes CT the one subdomain source that
is both complete for anything HTTPS-served and entirely public.

What that rules out, which is the point:

| The usual approach | Why CT is better |
|---|---|
| Brute-force wordlist | only finds names you already guessed; CT lists what exists |
| DNS zone transfer | almost always refused, and noisy when attempted |
| Hammering a resolver | thousands of queries against someone else's infrastructure |

**Nothing here contacts the target.** No probing, no resolving, no port scan, no
connection to any host found. The certificates were published by the CAs, and
this reads the published record.

**Per hostname**, deduplicated to one row each: the hostname, whether it is a
wildcard, subdomain depth, first and last seen, how many certificates covered
it, the issuing CAs, and whether a covering certificate is currently valid.

**`includeExpired`** is off by default, which gives the hosts that are live now.
Turn it on for the historical view, where decommissioned staging and legacy
hosts show up, and that is often exactly what a security review wants.

Raw CT output for one domain is routinely ten times duplicates, since a
certificate carries many SANs and names repeat across every renewal.
Deduplicating is not a nicety here, it is the difference between billing for 958
rows and for 94 real hosts.

### Do you need an API key? No. Is there a free tier? Yes.

The two things people search for in this category, measured, are **free** and
**API key**: `subdomain finder api free`, `subdomain finder api key`.

So, plainly:

- **No API key.** Nothing to register for, nothing to rotate, no key to leak in
  a repo. The source is a public API published deliberately by its owner.
- **No proxy setup.** The source does not bot-wall datacenter addresses, so the
  default works.
- **Pay per result, not per month.** There is no subscription and no minimum.
  Rows that error or that your filters drop are **not charged**.
- **Free to try.** Run it with the prefilled input and see real rows before
  deciding anything.

### Notes

- **Public record only.** CT logs are a published, append-only, legally mandated transparency mechanism. This reads them. It does not probe, resolve, port-scan or connect to any host it finds.
- A hostname in CT means a certificate was issued for it. It does **not** guarantee the host resolves or is reachable today: `certificateActive` tells you about the certificate, not about DNS.
- **Two CT front ends, not one.** crt.sh is tried first because it has the deeper history. When it will not serve the request, the run falls back to Cert Spotter automatically and you get the same fields either way. Certificate Transparency is a public append-only log with several readers, so depending on a single front end was never a good reason to fail a run.
- Queries against large domains can take 10-20 seconds; crt.sh runs a database scan behind an HTTP front end, and the client timeout is raised to 90s to accommodate it.
- Under load crt.sh sometimes answers with an HTML error page instead of an HTTP error code. That is detected and reported as a retryable failure rather than parsed as empty results.
- **A 404 from crt.sh is not "no certificates found."** crt.sh reports that as HTTP 200 with an empty list; a 404 is the service refusing traffic. Reading the two as the same thing is how a subdomain tool tells you a domain with thousands of logged certificates has none, and exits successfully. The refusal is retried on a fresh IP, then failed over, and only ever reported as what it is.

### FAQ

#### How is this different from a brute-force subdomain finder?

A wordlist tool fires thousands of DNS queries and finds only names someone
thought to add to the list. Certificate Transparency is a published, append-only
log that every public CA is required to write to, so any host ever served over
HTTPS is already public record, including the ones nobody would guess. No DNS
hammering, and nothing that looks like an attack in someone's logs.

#### Is this legal?

It reads a legally mandated public transparency mechanism. It does not probe,
resolve, port-scan or connect to any host it finds. Use it on domains you own or
are authorised to assess. That authorisation is your responsibility, not a
property of the data.

#### Does a hostname in the results mean the host is live?

No, and this is the most common misreading. A hostname in CT means a certificate
was issued for it. `certificateActive` tells you whether a certificate is valid
right now. It says nothing about whether DNS resolves or the host answers.

#### How do I find decommissioned or forgotten hosts?

Set `includeExpired: true`. The default returns hosts with unexpired
certificates, which is the current attack surface; expired entries are where old
staging servers and forgotten legacy hosts live, and in a security review that is
usually the interesting half.

#### Am I charged per certificate or per hostname?

Per unique hostname. Raw CT output for a mid-size domain is routinely ten times
more records than there are real hosts, because one certificate carries many SANs
and every 90-day renewal republishes them all. A raw query against `apify.com`
returns about 958 certificate records; you are billed for the distinct hostnames
that come out of them.

#### Why is a run slow?

crt.sh runs a database scan behind an HTTP front end, so large domains can take
10-20 seconds. The client timeout is raised to 90s for that reason.

#### Can I find wildcard certificates?

Yes. They arrive as `*.example.com` rows with `isWildcard: true`, so you can
include or filter them deliberately.

# Actor input Schema

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

Root domains to enumerate, one per line, e.g. apify.com. A full URL is accepted and trimmed down for you.

## `includeExpired` (type: `boolean`):

Off by default, which gives you the hosts that are live now. Turn it on for a historical view, where decommissioned staging and legacy hosts show up, and that is often exactly what a security review wants.

## `includeWildcards` (type: `boolean`):

Include \*.example.com entries. They prove a wildcard certificate exists but name no specific host, so they are excluded by default.

## `maxHostsPerDomain` (type: `integer`):

Cap per domain, applied after deduplication. The form is prefilled with 50 so a first run costs cents; the cap when this field is left out entirely is 5000.

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

Proxy settings.

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

A hard ceiling on rows for the entire run, across every target. The per-target limits above cap each one separately, so fifty targets at twenty each is still a thousand rows; this caps the total. The run stops cleanly when it is reached and nothing beyond that point is fetched or charged. Leave empty for no ceiling.

## Actor input object example

```json
{
  "domains": [
    "apify.com"
  ],
  "includeExpired": false,
  "includeWildcards": false,
  "maxHostsPerDomain": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Hostname, depth, first and last seen, issuing CA, and whether a certificate is currently valid.

# 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"
    ],
    "maxHostsPerDomain": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("glitchbound/subdomain-scraper").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"],
    "maxHostsPerDomain": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("glitchbound/subdomain-scraper").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"
  ],
  "maxHostsPerDomain": 50
}' |
apify call glitchbound/subdomain-scraper --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/1veUgpdkp7D3wZ4CY/builds/H46CoJxZrauU5cWwf/openapi.json
