# Subdomain Finder: certificate transparency logs (`examsens.ai/subdomain-finder`) Actor

Finds the subdomains of any domain from certificate transparency logs (crt.sh), with first and last certificate dates, issuers, wildcard flags and current DNS addresses. No start fee; you pay per subdomain returned.

- **URL**: https://apify.com/examsens.ai/subdomain-finder.md
- **Developed by:** [Ed Rayyis](https://apify.com/examsens.ai) (community)
- **Categories:** Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.72 / 1,000 subdomain returneds

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?

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

## Subdomain Finder: certificate transparency logs

List one or more domains and get back the subdomains that have appeared on public TLS certificates, one row each. crt.sh indexes the certificates recorded in public certificate transparency logs, so hosts named on those certificates show up here, staging and test hosts included.

The search runs against crt.sh \[1], a free public certificate transparency search. No scanning or guessing of names takes place. DNS lookups, on by default, go to public resolvers, not to the domain's web servers.

### What you get for each subdomain

- `subdomain` and `level` (1 for `blog.example.com`, 2 for `api.eu.example.com`)
- `firstSeen` and `lastSeen`: the earliest and latest start dates of certificates naming it
- `lastCertificateExpiry` and `hasValidCertificate`
- `logEntries`: how many log entries name it. A certificate can be logged twice, once as a precertificate and once as the final certificate, so this can run higher than the number of certificates
- `issuers`, the organization names of the certificate authorities, such as Let's Encrypt
- `wildcardCertificate` when the name appeared as `*.name`, and `namedInCertificate` when it appeared spelled out
- `resolves` and `ipAddresses`, the current A and AAAA records, when DNS lookups are on (the default). `resolves` is empty when the lookup times out

A name taken from a wildcard certificate is returned without the `*.`, so `*.staging.example.com` gives `staging.example.com`. Email addresses that some certificates carry are dropped, and so is the domain itself.

### Input

- Domains (required). A URL works too; `https://www.python.org/about/` is searched as `python.org`.
- Include names from expired certificates, on by default.
- Look up DNS addresses, on by default.
- Only subdomains that resolve, off by default. Names with no A or AAAA record are left out and aren't charged.
- Maximum subdomains per domain, 1,000 by default, shallowest names first.

### Output

One row per subdomain. A domain that isn't a valid domain name, or that has no subdomains in the logs, gets one row with `ok` false and the reason in `error`, and the run moves on to the next domain. When crt.sh can't be reached after four tries, the reason goes in `error` too. The `OUTPUT` record in the key-value store gives the number of subdomains returned and domains that failed.

### Speed and fair use

crt.sh limits each IP address to 60 requests a minute \[2], so the actor sends one request at a time, at least 1.5 seconds apart. crt.sh is a free public service and is sometimes busy; when it answers with a server error or times out, the actor waits 5, then 10, then 20 seconds and asks again.

### Pricing

Pay per event: one event per subdomain returned. There's no start fee and no charge for domains that fail or find nothing. When the run reaches the maximum charge you set, it stops, and every subdomain row in the dataset is a paid one.

### Support

Questions and problems: the Issues tab on this page.

### References

1. Certificate Search | crt.sh | crt.sh. https://crt.sh/
2. Rate limits for https://crt.sh/ | crt.sh discussion group | groups.google.com. https://groups.google.com/g/crtsh/c/NZJntKrBdmg

# Actor input Schema

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

Domains to search, such as example.com. A full URL also works: the host name is taken from it, without www.

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

Keep subdomains whose certificates have all expired. Turn off to see only names on certificates that are still valid.

## `resolveDns` (type: `boolean`):

Look up the A and AAAA records of every subdomain found, so you can tell live hosts from old ones.

## `onlyResolving` (type: `boolean`):

Return only subdomains with at least one A or AAAA record. Names that don't resolve are left out and not charged.

## `maxSubdomainsPerDomain` (type: `integer`):

Stop after this many subdomains for each domain, shallowest names first.

## Actor input object example

```json
{
  "domains": [
    "apify.com"
  ],
  "includeExpired": true,
  "resolveDns": true,
  "onlyResolving": false,
  "maxSubdomainsPerDomain": 1000
}
```

# Actor output Schema

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

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

# Run the Actor and wait for it to finish
run = client.actor("examsens.ai/subdomain-finder").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": [
    "apify.com"
  ]
}' |
apify call examsens.ai/subdomain-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,examsens.ai/subdomain-finder"
        }
    }
}
```

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/Kgbk26POeObraypnU/builds/Pt2jLThT86dzAdD4q/openapi.json
