How to Use a DNS Lookup API to Retrieve DNS Records
Query A, AAAA, MX, TXT and other DNS records over HTTPS, then handle TTLs, errors, caching and provider-specific APIs.
A DNS lookup API lets your application retrieve records without opening a command-line resolver. For public, resolver-visible answers, send the domain name and record type to a DNS-over-HTTPS endpoint such as Google’s JSON API: https://dns.google/resolve?name=example.com&type=A. For records stored in a DNS provider account, use that provider’s authenticated management API, such as Cloudflare’s GET /zones/{zone_id}/dns_records.
The distinction matters: a public resolver reports what it can currently resolve, while a management API reports records configured in a zone you control. The examples below show both approaches, including cURL, Python and Node.js.
Choose the right DNS API
| Approach | Returns | Authentication | Best for | Main caveat |
|---|---|---|---|---|
| Public DNS-over-HTTPS resolver | The resolver’s current answer for a name and type | Usually none | Diagnostics, propagation checks and client-facing lookups | Resolver cache, DNSSEC and policy affect the answer |
| Authenticated DNS-management API | Records stored in a DNS zone | Scoped API token or key | Inventory, audits and automation | Provider-specific schema and permissions |
Query records with Google’s JSON DNS API
Google documents https://dns.google/resolve as a GET-only JSON API. Pass name and type; common types include A, AAAA, MX, NS, CNAME, TXT and SOA. The response includes DNS status fields and an answer array when records are available. See the Google Public DNS JSON API documentation.
cURL
curl --fail-with-body --get 'https://dns.google/resolve' \
--data-urlencode 'name=example.com' \
--data-urlencode 'type=A'
Python
import requests
name = "example.com"
record_type = "A"
response = requests.get(
"https://dns.google/resolve",
params={"name": name, "type": record_type},
timeout=10,
)
response.raise_for_status()
data = response.json()
if data.get("Status") != 0:
raise RuntimeError(f"DNS status: {data.get('Status')}")
for answer in data.get("Answer", []):
print({
"name": answer.get("name"),
"type": answer.get("type"),
"ttl": answer.get("TTL"),
"data": answer.get("data"),
})
Node.js
const params = new URLSearchParams({ name: 'example.com', type: 'A' });
const response = await fetch(`https://dns.google/resolve?${params}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
if (data.Status !== 0) throw new Error(`DNS status ${data.Status}`);
for (const answer of data.Answer ?? []) {
console.log({
name: answer.name,
type: answer.type,
ttl: answer.TTL,
data: answer.data,
});
}
Interpret the JSON response
| Field | Meaning |
|---|---|
Status |
DNS response code. A non-zero value indicates a DNS-level result such as an error or NXDOMAIN. |
Answer |
Returned records. It can be absent or empty when there is no answer. |
Answer[].name |
The owner name of the record. |
Answer[].type |
The numeric DNS type in Google’s JSON response. |
Answer[].TTL |
Remaining cache lifetime reported by the resolver, in seconds. |
Answer[].data |
The record value, such as an IPv4 address, mail exchanger or TXT string. |
Query a Cloudflare-managed zone
When you need configured records rather than a recursive resolver’s answer, use Cloudflare’s DNS Records API. Cloudflare describes this endpoint as one that can “List, search, sort, and filter a zones’ DNS records.” Create an API token with DNS Read permission, identify the zone ID, and call GET /zones/{zone_id}/dns_records. The Cloudflare DNS records documentation lists filters and response fields.
cURL
export CLOUDFLARE_API_TOKEN='replace-me'
export ZONE_ID='replace-me'
curl --fail-with-body \
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?name=example.com&type=A" \
--header "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
--header 'Content-Type: application/json'
Python
import os
import requests
zone_id = os.environ["ZONE_ID"]
token = os.environ["CLOUDFLARE_API_TOKEN"]
response = requests.get(
f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
params={"name": "example.com", "type": "A"},
timeout=10,
)
response.raise_for_status()
payload = response.json()
if not payload.get("success"):
raise RuntimeError(payload.get("errors"))
for record in payload["result"]:
print({
"id": record["id"],
"name": record["name"],
"type": record["type"],
"ttl": record["ttl"],
"content": record["content"],
})
Node.js
const zoneId = process.env.ZONE_ID;
const token = process.env.CLOUDFLARE_API_TOKEN;
const params = new URLSearchParams({ name: 'example.com', type: 'A' });
const response = await fetch(
`https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records?${params}`,
{ headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } }
);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const payload = await response.json();
if (!payload.success) throw new Error(JSON.stringify(payload.errors));
for (const record of payload.result) {
console.log({ id: record.id, name: record.name, type: record.type, ttl: record.ttl, content: record.content });
}
To retrieve one known record, use GET /zones/{zone_id}/dns_records/{dns_record_id}. Keep the token scoped to read access when the application only needs inventory.
Use DNS-over-HTTPS wire format when interoperability matters
Google also exposes https://dns.google/dns-query, an RFC 8484 endpoint supporting GET and POST. Send the required DNS media type, typically application/dns-message, and parse the binary DNS response with a DNS library. JSON is easier for scripts, but Cloudflare notes that there is no agreed JSON schema for DNS-over-HTTPS; implementations that must work across providers should normalize each provider’s JSON or use the standardized wire format. See Cloudflare’s DoH documentation.
Record types and fields
- A: maps a host name to an IPv4 address.
- AAAA: maps a host name to an IPv6 address.
- MX: identifies mail exchangers; values include a priority and host name.
- CNAME: aliases one name to another.
- NS: identifies authoritative name servers.
- TXT: carries text, often verification or policy data.
- SOA: describes the authority for a DNS zone.
Provider schemas differ. Cloudflare records commonly expose content; Google Cloud DNS resource record sets expose name, type, ttl and rrdatas. The Google Cloud ResourceRecordSet reference defines TTL as the number of seconds that a resource record set can be cached by resolvers.
Normalize input and output safely
- Trim whitespace and convert the name to a consistent case for display.
- Choose an explicit type instead of relying on a provider default.
- Use URL encoding for names and query parameters.
- Preserve the resolver or provider name and lookup timestamp in diagnostics.
- Normalize records into a common shape such as
{name, type, ttl, values}, while retaining the original payload for debugging. - Do not assume an empty answer means the domain is broken. Distinguish NOERROR with no records, NXDOMAIN, timeout and authorization failure.
TTL, caching and propagation
TTL is a cache lifetime, measured in seconds. It does not guarantee that every resolver refreshes at exactly the same instant. A recently changed record can remain visible through older cached answers until those TTLs expire. For propagation checks, query more than one resolver and record the resolver address, time, status and TTL.
Errors and troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| HTTP 400 | Missing or malformed name/type parameter | URL-encode the domain and use a supported record type. |
| HTTP 429 | Provider rate limit | Apply exponential backoff, cache repeated lookups and follow the provider’s limits. |
| DNS status indicates NXDOMAIN | The queried name does not exist according to that resolver | Check spelling, delegation and whether you queried the intended environment. |
| NOERROR with no Answer | No record of that type, or an empty response section | Try the authoritative or management API and check related records such as CNAME. |
| Cloudflare 401/403 | Missing, expired or insufficiently scoped token | Use a token with DNS Read permission and verify the zone ID. |
| Cloudflare result is empty | Filter does not match the stored name/type | Remove filters, inspect the zone, then add name and type filters. |
| Timeout | Network or resolver delay | Set a finite timeout, retry a small number of times and report the timeout separately from an empty answer. |
| Unexpected JSON fields | Provider schemas are different | Parse the selected provider’s documented schema and normalize it at your application boundary. |
Performance, reliability and cost
- Cache by name and type: respect the returned TTL where appropriate, with a maximum cache age for your application.
- Reuse connections: use a persistent HTTP client or keep-alive agent for batches.
- Bound concurrency: avoid sending an unbounded burst of lookups; combine caching with a queue.
- Retry selectively: retry transient network failures and 5xx responses, but do not blindly retry NXDOMAIN, 401, 403 or malformed requests.
- Separate data sources: label recursive answers and zone inventory differently in storage and UI.
- Protect secrets: keep management API tokens server-side, use environment variables or a secret manager, and never expose them in browser code.
- Control cost: public resolvers may be free but still impose operational limits; management APIs have provider-specific quotas and plan rules. Caching reduces both request volume and latency.
Or skip the browser setup
If your workflow also needs screenshots of DNS dashboards, documentation pages or other web pages, ScreenshotNeo provides a single GET request that returns a PNG, JPEG, WebP or PDF. Read the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Cookie banners, newsletter popups and chat widgets are removed before the shot. Bot checks, blank pages and failed loads are never billed. An MCP server lets AI agents take screenshots, and 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
FAQ
Can a public DNS API show the authoritative record?
It shows the recursive resolver’s current view. Use the DNS provider’s management API or query authoritative servers when you need zone data.
Should I query A and AAAA separately?
Yes. They are different record types and can have different values, TTLs and availability.
Why does a TXT value contain quotes or split strings?
TXT records are represented according to DNS and provider serialization rules. Preserve the raw value and normalize only for your application’s display or validation.
When should I use Cloudflare’s single-record endpoint?
Use it when you already know the record ID and need one record; use the list endpoint for searches, filters or inventory.
Is JSON DoH portable between providers?
No universal JSON schema is defined. Normalize provider responses or use RFC 8484 wire format for cross-provider tooling.


