# Bulk DNS Records Lookup (`plainpipe/dns-records-lookup`) Actor

Bulk DNS lookup: A, AAAA, CNAME, MX, NS, TXT, SOA, CAA records, SPF and DMARC parsed, and setup problems per domain. Use when you need DNS for many domains.

- **URL**: https://apify.com/plainpipe/dns-records-lookup.md
- **Developed by:** [Plain Pipe](https://apify.com/plainpipe) (community)
- **Categories:** SEO tools, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.60 / 1,000 results

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

## Bulk DNS Records Lookup

Look up A, AAAA, CNAME, MX, NS, TXT, SOA and CAA records for many domains at once, with SPF and DMARC parsed and a list of DNS setup problems per domain.

### What it does

Give it a list of domains. For each one it asks a DNS resolver for the record types you pick and returns one row per domain with the records grouped by type. It also reads the mail-security records (SPF from the domain's TXT records, DMARC from `_dmarc.<domain>`) and turns them into plain facts: is SPF present, which includes it uses, how it ends (`-all`, `~all`, ...), what the DMARC policy is. A short problem list flags common gaps such as no SPF, no DMARC, DMARC `p=none`, more than one SPF record or no mail server (MX).

A domain that does not exist is still returned, with status `NXDOMAIN`, so you can see which names in your list are not registered or not delegated.

### Use cases

- Audit SPF and DMARC across a portfolio of domains before tightening mail policy.
- Check that MX, NS and A records point where they should after a DNS or hosting migration.
- Review a client's DNS before a launch: CAA, SOA serial, name servers, IPv6 (AAAA) coverage.
- Find which names in a long list no longer resolve (`NXDOMAIN`).
- Compare answers from different public resolvers when a change is still propagating.

### How to use it

1. Put the domains into **Domains**, one per line. Full URLs are fine; only the host name is used.
2. Optionally change **Record types** (default: all except SRV).
3. Under Advanced settings, optionally pick a **DNS resolver** or turn on **Also check HTTPS on the apex**.
4. Run the Actor. Results land in the dataset; export them as JSON, CSV or Excel, or read them through the Apify API.
5. Filter rows on `status` or on a non-empty `problems` list to find the domains that need work.

### Input

| Field | What it does | Default | Example |
|---|---|---|---|
| `domains` (required) | Domains to look up. `https://www.python.org/about/` is read as `www.python.org`. Duplicates are looked up once. Any entry that is not a valid domain name fails the run before any lookup (no charge). | none | `["apify.com", "python.org"]` |
| `recordTypes` | Any of `A`, `AAAA`, `CNAME`, `MX`, `NS`, `TXT`, `SOA`, `CAA`, `SRV` (case does not matter). | all except `SRV` | `["A", "MX", "TXT"]` |
| `dnsServer` | `system` (the run's own resolver), `1.1.1.1`, `8.8.8.8` or `9.9.9.9`. | `system` | `"1.1.1.1"` |
| `checkHttps` | If `true`, sends one HEAD request to `https://<domain>/` (robots.txt respected) and adds an `https` object with status code and response time. | `false` | `true` |
| `maxItems` | Look up at most this many domains. | `100` | `1000` |

With `SRV`, a fixed list of common service names is queried: `_sip._tcp`, `_sip._tls`, `_sipfederationtls._tcp`, `_xmpp-client._tcp`, `_xmpp-server._tcp`, `_autodiscover._tcp`, `_caldavs._tcp`, `_carddavs._tcp`, `_imaps._tcp`, `_submission._tcp`.

### Output

One row per domain. Example for `python.org` (records as published on 2026-11-03; the TXT list and one SPF include are shortened here):

```json
{
  "domain": "python.org",
  "status": "OK",
  "resolver": "1.1.1.1",
  "records": {
    "A": ["151.101.0.223", "151.101.128.223", "151.101.64.223", "151.101.192.223"],
    "AAAA": ["2a04:4e42:600::223", "2a04:4e42:400::223", "2a04:4e42:200::223", "2a04:4e42::223"],
    "CNAME": [],
    "MX": [{ "exchange": "mail.python.org", "priority": 50 }],
    "NS": ["ns-1134.awsdns-13.org", "ns-2046.awsdns-63.co.uk", "ns-484.awsdns-60.com", "ns-981.awsdns-58.net"],
    "TXT": ["MS=73147F1EC0843C399CF17F586EC6B8EAF8C57961", "v=spf1 mx ip4:188.166.95.178/32 ip6:2a03:b0c0:2:d0::71:1 include:stspg-customer.com ... include:mailgun.org ~all"],
    "SOA": { "primaryNs": "ns-2046.awsdns-63.co.uk", "serial": 1, "refresh": 7200, "retry": 900, "expire": 1209600, "minTtl": 86400 },
    "CAA": []
  },
  "spf": { "present": true, "record": "v=spf1 mx ip4:188.166.95.178/32 ... ~all", "count": 1, "includes": ["stspg-customer.com", "...", "mailgun.org"], "redirect": null, "allQualifier": "~" },
  "dmarc": { "present": true, "count": 1, "policy": "none", "subdomainPolicy": "none", "pct": 100, "ruaPresent": true },
  "problems": ["DMARC policy is p=none (monitoring only)"],
  "queryErrors": {},
  "checkedAt": "2026-11-03T21:20:00.000Z"
}
```

| Field | Meaning |
|---|---|
| `status` | `OK`, `NXDOMAIN` (the name does not exist), `SERVFAIL` or `NO_ANSWER` (the resolver answered but returned nothing usable). |
| `records` | Only the types you asked for. Empty list (or `null` for SOA) means no record of that type. |
| `spf` | First `v=spf1` record, its includes, redirect and `all` qualifier, and how many SPF records exist. |
| `dmarc` | Policy (`p`), subdomain policy (`sp`), `pct`, and whether aggregate reports are requested (`ruaPresent`). Report destinations are not returned. |
| `problems` | Plain-language list of common setup gaps. |
| `queryErrors` | Per-type resolver errors other than "no data", for example `{ "CAA": "ESERVFAIL" }`. |
| `https` | Only with `checkHttps`: `{ url, reachable, statusCode, finalUrl, responseTimeMs, note }`. |

A CAA `iodef` mailbox is shown as `mailto:(set, not shown)`.

### Pricing

Pay per result: **$0.60 per 1,000 results** ($0.0006 per result). You only pay for results the Actor returns. Set a maximum total charge on the run to cap your spend.

### Limits

- Answers come from a recursive resolver, so they can be cached for up to the record's TTL. It does not query authoritative servers directly and does not validate DNSSEC.
- CNAME is read at the name you give. Apex domains rarely have one; `www.` names often do. A and AAAA follow CNAMEs.
- SPF is parsed from the first `v=spf1` record only; includes are listed, not followed recursively, and the 10-lookup limit is not counted.
- DKIM is not checked: it needs a selector name that is not discoverable through DNS.
- SRV covers only the fixed service list above.
- If the resolver does not answer at all for a domain (timeout), that domain produces no row and costs nothing.
- Up to 5 domains are looked up in parallel. Very large lists take a while; split them if you need speed.

### FAQ

**How much does it cost?** $0.0006 per domain returned, so 1,000 domains cost $0.60. A domain that does not exist (`NXDOMAIN`) is a result; a domain whose resolver never answers is not charged.

**Why did my run fail before any lookup?** One entry was not a valid domain name (for example text with spaces or an address with `@`), a record type was misspelled, or the list was empty. The error names what to fix, and nothing is charged.

**Why do results differ from another tool?** Resolvers cache answers for the record's TTL. Pick a different **DNS resolver** to compare, or wait for the TTL to pass after a change.

**Can I run it regularly to spot DNS changes?** Yes. Create an Apify Schedule (Console, Schedules) for the Actor or a saved task, for example daily. Each run returns one row per domain, so cost is domains times runs; compare `records` or the SOA `serial` between runs.

**Can I connect it to other tools?** Yes. Export the dataset as JSON, CSV or Excel, or start runs and read results through the Apify API, webhooks and integrations such as Zapier and Make.

### Legal and data

This Actor processes only what you give it or public, non-personal technical data. It does not log in anywhere, does not collect personal data, and does not bypass paywalls, CAPTCHAs or access controls. You are responsible for having the right to process the inputs you provide.

### Support

Open an issue on the Issues tab. Issues are answered within 14 days.

**Use this when:** you need the DNS records, SPF and DMARC policy or DNS setup gaps of one or many domains, as rows.

### Why this one

- $0.60 per 1,000 results ($0.0006 per domain); domains whose resolver never answers are not charged.
- Each row has parsed SPF (includes, `all` qualifier, record count) and DMARC (`p`, `sp`, `pct`), a per-type `queryErrors` map and a plain-language `problems` list, next to the raw records.
- Pure DNS queries, with a choice of system resolver or 1.1.1.1, 8.8.8.8 and 9.9.9.9: no browser, no proxy and no web requests unless you turn on the HTTPS check. The 2-domain prefill run finishes in a few seconds.
- The prefilled input is test-run daily.

Built and maintained by Plain Pipe.

# Actor input Schema

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

Domains to look up, one per line, for example apify.com or www.python.org. A full URL such as https://www.python.org/about/ is reduced to its host name. Internationalized names are converted to ASCII. Every entry must be a valid domain name, or the run fails before any lookup.

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

DNS record types to return. Default: all except SRV, for example \["A", "MX", "TXT"] for a quick mail check. With SRV on, a fixed list of common service names (\_sip.\_tcp, \_xmpp-server.\_tcp, \_autodiscover.\_tcp and similar) is queried. SPF and DMARC are always parsed, whatever you pick here.

## `dnsServer` (type: `string`):

Which resolver answers the queries: the system resolver of the run (default), or a public resolver by IP address, for example 1.1.1.1 to compare answers.

## `checkHttps` (type: `boolean`):

If on, sends one HEAD request to https://<domain>/ (robots.txt respected) and adds the status code and response time to the row. Default off: the lookup itself makes no web requests.

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

Look up at most this many domains (one billable result each). Default 100; for example 1000 for a large portfolio.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "python.org"
  ],
  "recordTypes": [
    "A",
    "AAAA",
    "CNAME",
    "MX",
    "NS",
    "TXT",
    "SOA",
    "CAA"
  ],
  "dnsServer": "system",
  "checkHttps": false,
  "maxItems": 10
}
```

# 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",
        "python.org"
    ],
    "recordTypes": [
        "A",
        "AAAA",
        "CNAME",
        "MX",
        "NS",
        "TXT",
        "SOA",
        "CAA"
    ],
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("plainpipe/dns-records-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",
        "python.org",
    ],
    "recordTypes": [
        "A",
        "AAAA",
        "CNAME",
        "MX",
        "NS",
        "TXT",
        "SOA",
        "CAA",
    ],
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("plainpipe/dns-records-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",
    "python.org"
  ],
  "recordTypes": [
    "A",
    "AAAA",
    "CNAME",
    "MX",
    "NS",
    "TXT",
    "SOA",
    "CAA"
  ],
  "maxItems": 10
}' |
apify call plainpipe/dns-records-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,plainpipe/dns-records-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/7evOZ0AwE235O0gkr/builds/4FWWCWsOBZLMgt5IF/openapi.json
