# DNS Record Lookup — A, MX, TXT, NS, CNAME, SOA API (`accountable_eel/dns-record-lookup`) Actor

Look up DNS records for any domain — A, AAAA, MX, TXT, NS, CNAME, or SOA — straight from Cloudflare's public DNS-over-HTTPS resolver, with DNSSEC validation status per record. Paste domains, press Start. Charged once per domain that resolves — NXDOMAIN and empty responses cost nothing.

- **URL**: https://apify.com/accountable\_eel/dns-record-lookup.md
- **Developed by:** [Adrian Voss](https://apify.com/accountable_eel) (community)
- **Categories:** Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 successful lookups

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?

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

## DNS Record Lookup

You give this actor a list of domains, and it queries their live DNS records — A,
AAAA, MX, TXT, NS, CNAME, or SOA — straight from
[Cloudflare's public DNS-over-HTTPS resolver](https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/),
the same infrastructure behind `1.1.1.1`. No API key, no scraping, no zone
transfer — just the answer a DNS resolver already gives out publicly, returned
as one flat row per domain (or one row per record, if you want it that way).

### Who it's for

The accountable\_eel catalogue sells company intelligence columns for outbound. Each actor takes a
list of domains or company identifiers and returns one flat, stably-named row per input —
firmographics, registry IDs, tech stack, email route, hiring activity — the shape a Clay table, an
n8n workflow, or an AI agent can consume without post-processing. Pricing is pay-per-event and
per-domain: a few tenths of a cent for a row that was actually found, and nothing for a
miss, so a list that doesn't enrich costs you next to nothing. Where an official source exists —
VIES, GLEIF, SEC EDGAR, Brønnøysund, PRH, RDAP — it is queried directly instead of scraped. No
seat licence, no monthly minimum, no credit system to decode.

For DNS specifically, that means you paste a list of domains and get back what each one's
nameservers are actually publishing right now — mail routing, IP addresses, TXT-record policy,
DNSSEC status — without opening a terminal or writing a resolver loop yourself.

### Why this one

- **Queries a real resolver, not a scrape.** Every lookup goes to Cloudflare's public
  DNS-over-HTTPS endpoint over HTTPS — the same service that answers `1.1.1.1` — so there's
  nothing brittle to break when a target site changes its HTML.
- **All seven record types from one actor.** A, AAAA, MX, TXT, NS, CNAME, SOA — pick per domain
  by appending `:TYPE` to the input, instead of running seven separate tools.
- **DNSSEC and recursion signal on every row.** `dnssecValidated` and `recursionAvailable` come
  back with every lookup, useful for a security posture pass across a domain list without a
  separate DNSSEC checker.
- **Misses are free.** NXDOMAIN, SERVFAIL, and a NOERROR response with zero answer records are
  all classified as not-found and never billed — you only pay for a domain that actually
  resolved.
- **Row shape you choose.** `expandRows` gives you one row per DNS record (handy for a
  spreadsheet or a Clay table) or one row per domain with all its records nested in an array —
  either way, you're billed once per domain, not per record.

### What you get

Every row carries these fields. `domain` through `dnssecValidated` are the DNS-specific fields
(pulled straight from `.actor/profile.json`); the rest are the same envelope every actor in this
catalogue uses.

| Field | Type | Description |
|---|---|---|
| `query` | string | The input exactly as given — `example.com` or `example.com:MX` |
| `found` | boolean | `true` if the domain resolved for the queried type with at least one answer record |
| `status` | string | `"OK"` on a found row; `NOT_FOUND`, `BLOCKED`, `REQUEST_FAILED`, or `BAD_FORMAT` on a miss |
| `domain` | string | The queried domain, lowercased |
| `queriedType` | string | The DNS record type that was queried: `A`, `AAAA`, `MX`, `TXT`, `NS`, `CNAME`, or `SOA` |
| `records` | array | Every answer record Cloudflare returned for that type — `name`, `type` (numeric DNS type), `ttl`, and `data` per record |
| `recursionAvailable` | boolean | The resolver's `RA` flag — whether recursive lookups were available for this query |
| `dnssecValidated` | boolean | The resolver's `AD` (Authenticated Data) flag — `true` only when the response chain was cryptographically validated |
| `scrapedAt` | string (ISO 8601) | Timestamp of the lookup |
| `message` | string | Present only on miss rows — a human-readable reason (e.g. why the domain didn't resolve) |

With `expandRows` on (the default), each record in `records` becomes its own dataset row instead
of a nested array — same fields, one DNS record per row. If any `data` field name were to collide
with a reserved envelope key (`query`, `found`, `status`, `message`, `scrapedAt`), it would be
renamed to `<key>Value` in the output — that doesn't happen with this actor's current fields, but
it's the rule the shared harness applies across the whole catalogue so column names never
silently overwrite each other.

### Price

$4 per 1,000 domains, plus a $0.005 start fee. Misses (`found:false`) are never charged.

That's $0.004 per resolved domain at the FREE tier, dropping to $0.0024 per resolved domain on
paid tiers — the $0.005 actor-start charge is fixed regardless of tier. 1,000 domains through
this actor: **~$4** if every one resolves, less for any that come back NXDOMAIN or SERVFAIL. The
same 1,000 rows through a credit-based enrichment platform: **$80–$400**. A domain that doesn't
resolve for the queried type costs you nothing either way — you still get a row (`found: false`),
you just aren't billed for it.

### How to use

1. **In the Apify Console.** Open the actor page and click **Start** — the `domains` field is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found.
2. **Via the API.** Call it directly with a POST request — no Console needed once you have an API token:
   ```bash
   curl "https://api.apify.com/v2/acts/accountable_eel~dns-record-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"domains":["github.com"]}'
   ```
3. **On a schedule.** Save this actor as an Apify **Task** with the input you want, then add a **Schedule** (hourly, daily, weekly) so it runs on its own — no server of your own required.

1) Paste a list of domains into the input, one per line. `github.com` on its own queries the `A`
   record by default.
2) To query a different record type, append `:TYPE` — `github.com:MX`, `cloudflare.com:TXT`,
   `example.com:SOA`. Want two types for the same domain? List it twice, once per type — each is
   billed independently.
3) Turn on **Try it first** (`testRun`) to run just the first 5 items and check the shape of the
   output before committing to a full list.
4) Turn on **Hide rows with no result** (`onlyFound`) if you only want rows that actually
   resolved in your dataset — misses are free either way, this just keeps them out of the export.
5) Leave `maxConcurrency` at its default of 5 unless you have a reason to push it — this target
   has no browser fallback, so a burst of parallel requests risks getting rate-limited rather than
   finishing faster.
6) Run it, then export the dataset as CSV/JSON or pull it via the API into whatever pulls your
   enrichment data — Clay, n8n, a script, or an agent.

### Input

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

One domain per line. Defaults to an A record lookup; append :TYPE (A, AAAA, MX, TXT, NS, CNAME, or SOA) to query a different record type, e.g. github.com:MX. Accepted formats: github.com, github.com:MX.

```json
{
  "items": ["github.com", "github.com:MX", "cloudflare.com:TXT"],
  "maxConcurrency": 5
}
```

`items` is a list of domains to query. By default each domain is queried for its `A`
record; append `:TYPE` (one of `A`, `AAAA`, `MX`, `TXT`, `NS`, `CNAME`, `SOA`) to query a
different record type, e.g. `github.com:MX`. One dataset row is returned per item; rows
with `"found": false` are never charged. `maxConcurrency` controls how many DNS queries
run in parallel — this target has no browser fallback, so keeping it conservative avoids
getting rate-limited.

### Sample output

| query | found | status | domain | queriedType | records | recursionAvailable | dnssecValidated | scrapedAt |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| github.com | true | OK | github.com | A | <dns records> | true | false | 2026-08-24T06:01:23.530Z |

One row per item, for example:

```json
{
  "query": "github.com:MX",
  "found": true,
  "data": {
    "domain": "github.com",
    "queriedType": "MX",
    "records": [
      { "name": "github.com", "type": 15, "ttl": 3600, "data": "1 aspmx.l.google.com." }
    ],
    "recursionAvailable": true,
    "dnssecValidated": false
  },
  "scrapedAt": "2026-08-21T10:00:00.000Z"
}
```

Domains that don't resolve for the queried type — NXDOMAIN, SERVFAIL, or a NOERROR
response with no answer records — still get a row (`"found": false`), so you always get
one row per input, but you're never charged for those.

### Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.

```bash
curl "https://api.apify.com/v2/acts/accountable_eel~dns-record-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"domains":["github.com"]}'
```

**n8n.** Add an HTTP Request node: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~dns-record-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body Content Type `JSON`, JSON Body `{"domains":["github.com"]}` (swap in an expression from an earlier node for a real value).

**Clay.** Add an "HTTP API" column: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~dns-record-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body `{"domains":["{{domain}}"]}`, mapping the row's domain into the `domains` array.

**MCP.** In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "DNS Record Lookup | Apify" — the agent will find and run this actor.

### Tips

- **Query more than one record type for the same domain by listing it twice** —
  `example.com:A` and `example.com:MX` as separate input items. Each is billed
  independently as its own lookup, so you only pay for the types you actually ask for.
- **`dnssecValidated: false` doesn't mean "no DNSSEC."** It reflects Cloudflare's `AD` flag for
  that one query — `true` only when the resolver could cryptographically validate the response
  chain. A `false` can mean DNSSEC isn't configured, or just that validation wasn't attempted for
  that lookup. If you need a definitive DNSSEC posture, cross-check a `false` against the domain's
  `DNSKEY`/`DS` records directly.
- **Turn `expandRows` off if you want one row per domain.** With multiple `MX` or `TXT` records,
  the default (on) gives you one row per record — cleaner for filtering, but it multiplies row
  count on domains with a lot of records (still billed once per domain either way).
- **Keep `maxConcurrency` at 5 for large batches.** This target has no browser fallback to fall
  back on if Cloudflare starts throttling you, so slow-and-steady finishes a 10,000-domain list
  more reliably than a burst that gets rate-limited partway through.
- **Use `MX` and `TXT` together for a quick deliverability read** before a cold outreach or
  domain migration — `MX` tells you where mail routes, `TXT` surfaces SPF/DMARC records (look for
  `v=spf1` or `v=DMARC1` in the `data` field).
- **Resolve `A`/`AAAA`/`CNAME` across a domain list for infrastructure discovery** — the IP or
  CNAME target tells you which host, CDN, or cloud provider a domain points to, without visiting
  the site.
- **Check `NS` and `SOA` after a DNS cutover** to confirm propagation finished — query the same
  domain before and after a migration and diff the `records` array.
- **Run `dnssecValidated` across a portfolio you manage** as a lightweight security-posture pass —
  it won't replace a dedicated DNSSEC audit, but it flags which domains are worth a closer look.

### vs. alternatives

| | What it costs | What you get | Trade-off |
|---|---|---|---|
| **This actor** (`dns-record-lookup`) | $0.004 per resolved domain (FREE tier, down to $0.0024 on paid tiers), $0.005 actor start, nothing for NXDOMAIN/SERVFAIL | One row per domain or per record — A/AAAA/MX/TXT/NS/CNAME/SOA, plus DNSSEC and recursion flags, from one API call | One resolver (Cloudflare's), one record type per query item — no zone transfer, no historical record archive |
| **DIY** (`whois`/`dig`) | Free, your time | The same records, one domain at a time from a terminal | Fine for a handful of domains; across a list of thousands you're writing your own retry logic, rate limiting, and parsing to get a flat table out of it |
| **Clay** | $0.08–$0.40 per enriched row in credits, on top of a seat | A whole enrichment workspace — waterfalls across dozens of providers, plus the table and the sequencing around it | If you want one place that does everything and you're not counting rows, that's Clay. This is one column, priced per column, callable *from* Clay via its HTTP step. |

Prices for third-party tools are their published list prices as of August 2026 and are not
tracked here — check the vendor before relying on the comparison.

### FAQ

**What happens if a domain doesn't resolve — do I still pay?**
No. NXDOMAIN, SERVFAIL, and a NOERROR response with zero answer records for the queried type are
all treated as a miss (`found: false`, with a `status` of `NOT_FOUND`, `BLOCKED`, `REQUEST_FAILED`,
or `BAD_FORMAT`). Misses still produce a dataset row so your row count matches your input count,
but they're never billed.

**Do I need a Cloudflare API key?**
No. Cloudflare's DNS-over-HTTPS resolver is free and public — this actor calls it directly, with
no credentials required on your end.

**Are there rate limits I should worry about on a large batch?**
`maxConcurrency` (default 5) controls how many DNS queries run in parallel. This target has no
browser fallback, so keep it conservative on large runs — a burst of parallel requests risks
getting throttled rather than finishing faster. A list of a few thousand domains at the default
concurrency runs without issue.

**How fresh is the data — could I get a stale record?**
Every run queries Cloudflare's resolver live, so you get whatever it's currently serving. That
said, DNS itself is cached: Cloudflare's resolver respects each record's TTL, so a record that
changed in the last few minutes (or hours, for a long-TTL `SOA` or `NS` record) may not have
propagated to the resolver yet. This actor reports what the resolver has right now, not a
guaranteed real-time read of the authoritative nameserver.

**Do I need to configure a proxy?**
No. Requests go to Cloudflare's public resolver, not to the target company's own infrastructure,
so there's nothing to spoof or rotate around. The proxy configuration field is there because the
shared actor template includes it, but DNS-over-HTTPS to Cloudflare doesn't need one.

**Is DNS record data personal data under GDPR?**
DNS records describe a domain's infrastructure — IP addresses, mail servers, nameservers, TXT
policy strings — not people. They're not personal data, and this actor doesn't process, store, or
return anything about individuals.

**Can I schedule this to run on a cadence?**
Yes, through Apify's own Scheduler — set up a recurring run against a saved input (e.g. a
maintained list of domains you monitor) and it queries fresh on each run. There's no built-in
delta/diff feature here — every run is a full lookup against the current input list, so if you
want to know what changed between runs, keep the previous dataset export and diff it yourself
against the new one, or expand `TXT`/`MX`/`NS` and compare the record values.

**Can an AI agent call this directly?**
Yes. It's registered on the Apify MCP server — a client like Claude or Cursor can find and run it
by name ("DNS Record Lookup | Apify") — or call the Apify API directly over HTTP, same as the
`curl` example above.

### Related actors

- [Domain RDAP Lookup](https://apify.com/accountable_eel/domain-rdap-lookup) — registration data
  (registrar, creation/expiry dates, nameservers) for the same domain list, from RDAP instead of
  DNS.
- [Email Deliverability Check](https://apify.com/accountable_eel/email-deliverability-check) —
  mail route, disposable/role detection, and provider fingerprint for a domain, building on the
  same `MX`/`TXT` signal this actor surfaces raw.
- [Tech Stack Lookup](https://apify.com/accountable_eel/tech-stack-lookup) — what a domain is
  actually running (CMS, analytics, ecommerce platform), for when DNS tells you where it points
  but not what's there.

# Actor input Schema

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

One domain per line. Defaults to an A record lookup; append :TYPE (A, AAAA, MX, TXT, NS, CNAME, or SOA) to query a different record type, e.g. github.com:MX. Accepted formats: github.com, github.com:MX. You're only charged for the ones we actually find — a miss costs nothing.

## `testRun` (type: `boolean`):

Turn this on to test your input on a small sample before running the full list. Turn it off to process everything.

## `onlyFound` (type: `boolean`):

Only keep rows where something was actually found. Misses are always free, whether or not you show them here.

## `includeKeywords` (type: `array`):

Optional. Only keep results that mention at least one of these words (e.g. a job title, a city, a product name). Leave empty to keep everything.

## `excludeKeywords` (type: `array`):

Optional. Drop any result that mentions one of these words. Leave empty to skip nothing.

## `maxResults` (type: `integer`):

Optional. Stop the run once this many results have been found — useful for a quick, cheap sample. Leave blank for no limit.

## `columns` (type: `array`):

Choose which pieces of information to include in each result row. All are included by default.

## `expandRows` (type: `boolean`):

When on, each DNS record found gets its own row instead of being grouped under its domain. You're still only charged once per domain, no matter how many rows it produces.

## `maxConcurrency` (type: `integer`):

Parallel requests. Keep conservative — this target has no browser fallback, so getting blocked costs more than slow-and-steady.

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

Apify Proxy config. Residential recommended for anti-bot-sensitive targets.

## Actor input object example

```json
{
  "domains": [
    "github.com"
  ],
  "testRun": false,
  "onlyFound": false,
  "includeKeywords": [],
  "excludeKeywords": [],
  "columns": [
    "domain",
    "queriedType",
    "records",
    "recursionAvailable",
    "dnssecValidated"
  ],
  "expandRows": true,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# 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": [
        "github.com"
    ],
    "includeKeywords": [],
    "excludeKeywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("accountable_eel/dns-record-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": ["github.com"],
    "includeKeywords": [],
    "excludeKeywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("accountable_eel/dns-record-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": [
    "github.com"
  ],
  "includeKeywords": [],
  "excludeKeywords": []
}' |
apify call accountable_eel/dns-record-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,accountable_eel/dns-record-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/u90eLSbj0ajZuIFc8/builds/6WvTYvgm2FsbL0osS/openapi.json
