# Bulk DNS Records Lookup (`automation-lab/bulk-dns-lookup`) Actor

Resolve up to 5,000 domains into normalized A, AAAA, MX, NS, TXT, CNAME, and SOA records for infrastructure audits and scheduled DNS checks.

- **URL**: https://apify.com/automation-lab/bulk-dns-lookup.md
- **Developed by:** [Stas Persiianenko](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

Pay per event

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

## Bulk DNS Records Lookup

Resolve domain lists into normalized DNS records for infrastructure audits, migrations, vendor reviews, and scheduled monitoring.

The Actor accepts up to 5,000 domains or URLs and returns one dataset item per unique hostname. Each result can include A, AAAA, MX, NS, TXT, CNAME, and SOA records, lookup timing, per-record-type errors, and a stable status field.

It uses the DNS protocol directly, so no third-party API key or browser proxy is required.

### What can Bulk DNS Records Lookup do?

- Resolve seven common DNS record types in one run.
- Normalize URLs, Unicode hostnames, casing, and trailing dots.
- Deduplicate repeated hostnames before lookup and billing.
- Return IPv4 and IPv6 TTL values.
- Join multi-part TXT responses into complete strings.
- Sort records for easier comparison between scheduled snapshots.
- Continue when one record type fails and report that error on the domain item.
- Use the runtime resolver or optional custom DNS server IPs.
- Export results as JSON, CSV, Excel, XML, or RSS through Apify datasets.
- Feed scheduled results into webhooks, integrations, or your own change-detection pipeline.

### Who is this DNS records checker for?

**Infrastructure and SRE teams** can inventory DNS configuration before migrations or provider changes.

**Security teams** can review name servers, mail routing, TXT policies, and unexpected record changes across a domain portfolio.

**Email operations teams** can collect MX and TXT records for deliverability and configuration audits.

**Agencies and managed service providers** can run the same normalized check across customer domains.

**Data engineers** can enrich domain lists and send structured results to a warehouse or spreadsheet.

### DNS record types returned

| Field | DNS type | What it contains |
| --- | --- | --- |
| `a` | A | IPv4 addresses and TTL values |
| `aaaa` | AAAA | IPv6 addresses and TTL values |
| `mx` | MX | Mail exchanges and priorities |
| `ns` | NS | Authoritative name server hostnames |
| `txt` | TXT | Complete TXT values after joining response chunks |
| `cname` | CNAME | Canonical-name targets |
| `soa` | SOA | Primary server, hostmaster, serial, refresh, retry, expiry, and minimum TTL |

Missing record types are returned as empty arrays or `null` for SOA. They do not automatically make a domain an error.

### Why use this bulk DNS lookup?

A single DNS tool often answers one question for one hostname. This Actor is designed for repeatable lists:

1. one normalized row per unique domain;
2. the same requested record types on every row;
3. bounded concurrency for predictable bulk runs;
4. partial failure details instead of discarding an otherwise useful result;
5. dataset exports and Apify scheduling without additional infrastructure.

The Actor does not claim historical DNS data. To build history, schedule recurring runs and compare their timestamped datasets downstream.

### How to run a DNS records lookup

1. Open the Actor input page.
2. Add domain names or URLs to **Domains or URLs**.
3. Keep all record types selected, or select only those needed for your audit.
4. Optionally adjust concurrency, timeout, or custom DNS servers.
5. Click **Start**.
6. Open the dataset to inspect or export the normalized rows.

A useful first input is:

```json
{
  "domains": [
    "apify.com",
    "https://github.com/features",
    "cloudflare.com"
  ]
}
```

The URL is normalized to `github.com`; input order and spelling are retained in the `input` field.

### Input parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `domains` | string array | required | 1–5,000 domain names or URLs |
| `recordTypes` | string array | all seven types | Any of `A`, `AAAA`, `MX`, `NS`, `TXT`, `CNAME`, `SOA` |
| `concurrency` | integer | `20` | Domains processed in parallel, from 1 to 100 |
| `timeoutMs` | integer | `5000` | Per-query attempt timeout, from 500 to 30,000 ms |
| `dnsServers` | string array | runtime resolver | Optional recursive DNS server IP addresses |

Inputs are validated before lookups begin. Invalid domains, unsupported record types, or out-of-range settings fail the run rather than silently changing scope.

### Output example

The default dataset contains one object per unique normalized domain. This shortened example reflects the current output shape:

```json
{
  "input": "https://github.com/features",
  "domain": "github.com",
  "status": "success",
  "recordTypes": ["A", "AAAA", "MX", "NS", "TXT", "CNAME", "SOA"],
  "a": [{ "address": "140.82.121.4", "ttl": 46 }],
  "aaaa": [],
  "mx": [{
    "exchange": "github-com.mail.protection.outlook.com",
    "priority": 0
  }],
  "ns": ["dns1.p08.nsone.net", "dns2.p08.nsone.net"],
  "txt": [{ "value": "v=spf1 include:spf.protection.outlook.com ~all" }],
  "cname": [],
  "soa": {
    "nsname": "dns1.p08.nsone.net",
    "hostmaster": "hostmaster.nsone.net",
    "serial": 1656468023,
    "refresh": 43200,
    "retry": 7200,
    "expire": 1209600,
    "minttl": 3600
  },
  "recordCount": 13,
  "errors": {},
  "resolvedAt": "2026-08-05T06:06:53.770Z",
  "durationMs": 13
}
```

DNS answers change over time. Addresses, TTLs, record counts, and timestamps in a later run may differ.

### Understanding lookup status and errors

`success` means at least one record was found and no requested record type produced an operational DNS error.

`partial` means records were found, but one or more requested queries timed out or returned an operational resolver error.

`not_found` means no requested records were found and the resolver reported normal no-data or non-existent-name responses.

`error` means no records were returned and every requested record type produced an operational error.

The `errors` object is keyed by record type. A missing key means that query either succeeded or normally had no record.

### How much does it cost to look up DNS records?

Pricing uses one `start` event per run plus one `domain` event per unique normalized domain saved.

At the FREE tier, the current rates are **$0.005 per run** and **$0.00092 per domain**. Higher usage tiers receive lower per-domain rates automatically.

| Unique domains | Example FREE-tier charge |
| ---: | ---: |
| 1 | $0.00592 |
| 10 | $0.0142 |
| 100 | $0.097 |
| 1,000 | $0.925 |

Duplicate domains are resolved and charged once. Selecting more record types does not create additional charge events.

Apify platform compute charges and your account tier may affect the final run total shown in Console. Check the live pricing tab before a large production run.

### Scheduled DNS change monitoring workflow

The Actor produces snapshots, not built-in history. A recurring workflow can:

1. store the domain portfolio in an Apify Task;
2. schedule the Task hourly, daily, or weekly;
3. send the completed dataset to a webhook or integration;
4. compare each domain's sorted records with the previous snapshot;
5. notify the responsible team when an approved record changes.

SOA serials, NS changes, MX routing, and address records are common comparison targets. Treat TTL-only changes according to your own alert policy.

### Email and vendor configuration audit

For a focused email audit, request only MX and TXT:

```json
{
  "domains": [
    "stripe.com",
    "shopify.com",
    "salesforce.com",
    "hubspot.com"
  ],
  "recordTypes": ["MX", "TXT"]
}
```

This reduces DNS traffic while preserving the fields commonly used to inspect mail routing and published policy records. The Actor returns raw public TXT values; it does not judge SPF, DKIM, or DMARC policy correctness.

### Custom DNS resolvers

Set `dnsServers` when a workflow must query a particular recursive resolver:

```json
{
  "domains": ["apify.com", "github.com"],
  "dnsServers": ["1.1.1.1", "8.8.8.8"],
  "timeoutMs": 8000
}
```

Resolver IPs must be reachable from the Actor runtime. A private network address is not reachable unless your Apify environment provides that network path.

Different recursive resolvers may cache different TTLs or receive geo-aware answers. For comparable scheduled snapshots, keep the resolver configuration consistent.

### Export and integration options

Every result is stored in the default Apify dataset. You can:

- download CSV for a spreadsheet review;
- retrieve JSON through the dataset API;
- connect Google Sheets, Make, Zapier, or another Apify integration;
- trigger a webhook when the run succeeds or fails;
- schedule a saved Task for repeat audits;
- stream dataset items into a warehouse or monitoring service.

Nested arrays and objects are preserved most accurately in JSON. CSV exports serialize nested values.

### Run through the Apify API with cURL

Replace `APIFY_TOKEN` with your token:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~bulk-dns-lookup/runs?token=APIFY_TOKEN&waitForFinish=120" \
  -H "Content-Type: application/json" \
  -d '{
    "domains": ["apify.com", "github.com"],
    "recordTypes": ["A", "AAAA", "MX", "NS", "TXT", "CNAME", "SOA"]
  }'
```

Fetch results from the `defaultDatasetId` returned by the run.

### Run with JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/bulk-dns-lookup').call({
    domains: ['apify.com', 'github.com', 'cloudflare.com'],
    recordTypes: ['A', 'MX', 'NS', 'TXT', 'SOA'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Run with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/bulk-dns-lookup').call(run_input={
    'domains': ['apify.com', 'github.com', 'cloudflare.com'],
    'recordTypes': ['A', 'MX', 'NS', 'TXT', 'SOA'],
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use Bulk DNS Records Lookup through MCP

Add the Apify MCP server to Claude Code:

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

#### Claude Desktop

Add this remote MCP server in Claude Desktop's connector settings. The same JSON format is also usable by supported editor clients:

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

#### Cursor

Add the same `https://mcp.apify.com?tools=automation-lab/bulk-dns-lookup` URL as a remote MCP server in Cursor settings.

#### VS Code

Add the same remote URL to the MCP servers configuration used by your VS Code AI extension.

Example prompts:

- “Resolve A, MX, NS, TXT, and SOA records for these vendor domains and summarize any lookup errors.”
- “Run a DNS snapshot for my domain list and return the dataset URL.”
- “Check only MX and TXT records for these SaaS domains.”

### Limits and operational tips

- A run accepts at most 5,000 input entries.
- Duplicate normalized domains produce one item.
- Record order is normalized, but DNS answers can legitimately vary by resolver, location, and time.
- CNAME records commonly exist on subdomains and are often absent at an apex domain.
- TXT responses may contain verification values intended to be public but operationally sensitive; control access to exported datasets.
- Custom resolvers can rate-limit high concurrency. Lower `concurrency` or increase `timeoutMs` when needed.
- The Actor retries each DNS query once through the configured resolver.
- When the runtime's default resolver has a transient operational failure, one bounded retry uses independent public recursive resolvers. User-selected custom resolvers do not silently switch.
- A successful Actor run can contain `partial`, `not_found`, or `error` items; inspect item status in downstream workflows.

### Legality and responsible use

DNS records are publicly queryable infrastructure data. Use the Actor for lawful administration, research, security review, and monitoring.

Do not use results to misrepresent control of a domain, bypass access controls, or target systems without authorization. Follow applicable laws, contracts, and organizational policies. Avoid excessive repeated queries to custom resolvers you do not operate.

### FAQ and troubleshooting

#### Why is CNAME empty?

A domain can have A or AAAA records without a CNAME. Apex domains often cannot use CNAME records because other required record types coexist there. Query the exact subdomain you want to inspect.

#### Why does a domain show `not_found`?

The resolver returned no data for all requested types. Confirm spelling, query the intended hostname rather than a path, and try a known recursive resolver if local DNS behavior is in doubt.

#### Why does a result show `partial`?

At least one type returned records while another query timed out or encountered an operational resolver error. Review `errors`, increase `timeoutMs`, reduce concurrency, or choose a different DNS server.

#### Why did multiple inputs produce fewer rows?

The Actor normalizes hostnames and removes duplicates. For example, `APIFY.COM.`, `apify.com`, and `https://apify.com/docs` all resolve to one `apify.com` dataset item.

#### Does this Actor provide historical DNS records?

No. Each run captures current resolver answers. Schedule runs and retain datasets if you need your own change history.

### Related automation-lab Actors

- [Domain Availability Checker](https://apify.com/automation-lab/domain-availability-checker) — combine DNS and registration signals for bulk domain checks.
- [MX Record Checker](https://apify.com/automation-lab/mx-record-checker) — use when the workflow only needs mail exchanger records and provider identification.
- [WHOIS Lookup](https://apify.com/automation-lab/whois-lookup) — enrich DNS snapshots with public registration details.

### Support

If a run behaves unexpectedly, open an issue on the Actor page with:

- a minimal input that reproduces the behavior;
- the run URL;
- the expected record type;
- whether custom DNS servers were configured.

Do not include API tokens or private network details in an issue.

# Actor input Schema

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

Domain names or URLs to resolve. URLs are reduced to their hostnames, and duplicate normalized domains are looked up once. Maximum 5,000 entries.

## `recordTypes` (type: `array`):

Record types to query for every domain. By default, all supported types are resolved.

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

Number of domains resolved in parallel. Reduce this if a custom DNS server rate-limits requests.

## `timeoutMs` (type: `integer`):

Timeout in milliseconds for each DNS query attempt. Timed-out record types are reported in the item's errors object.

## `dnsServers` (type: `array`):

Optional DNS resolver IP addresses, for example 1.1.1.1 or 8.8.8.8. Leave empty to use the runtime's configured resolver.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "github.com",
    "cloudflare.com"
  ],
  "recordTypes": [
    "A",
    "AAAA",
    "MX",
    "NS",
    "TXT",
    "CNAME",
    "SOA"
  ],
  "concurrency": 20,
  "timeoutMs": 5000
}
```

# Actor output Schema

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

Dataset containing one normalized DNS lookup result per unique domain.

# 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",
        "github.com",
        "cloudflare.com"
    ],
    "recordTypes": [
        "A",
        "AAAA",
        "MX",
        "NS",
        "TXT",
        "CNAME",
        "SOA"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/bulk-dns-lookup").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",
        "github.com",
        "cloudflare.com",
    ],
    "recordTypes": [
        "A",
        "AAAA",
        "MX",
        "NS",
        "TXT",
        "CNAME",
        "SOA",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/bulk-dns-lookup").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",
    "github.com",
    "cloudflare.com"
  ],
  "recordTypes": [
    "A",
    "AAAA",
    "MX",
    "NS",
    "TXT",
    "CNAME",
    "SOA"
  ]
}' |
apify call automation-lab/bulk-dns-lookup --silent --output-dataset

```

## MCP server setup

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

```

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/7dhS5VabPXOPzI6Np/builds/z8NqjwuSRUvt2U0px/openapi.json
