You can ask for any DNS record over HTTP and get JSON back. No dig, no resolver library, nothing to parse. This page goes from the first request to a small function you can drop into a project, using the DNS Lookup API.
Your first lookup
You need an API key in the X-API-Key header. It's free, sign up here.
curl -G "https://apixies.io/api/v1/dns-lookup" \
-H "X-API-Key: YOUR_API_KEY" \
--data-urlencode "domain=github.com" \
--data-urlencode "type=A"
{
"status": "success",
"http_code": 200,
"code": "SUCCESS",
"message": "DNS lookup successful",
"data": {
"domain": "github.com",
"records": [
{ "type": "A", "host": "github.com", "ttl": 50, "ip": "140.82.121.3" }
],
"record_count": 1,
"types_queried": ["A"],
"timed_out": { "A": false },
"partial": false,
"queried_at": "2026-09-19T00:19:36+00:00"
}
}
Two parameters, that's all there is.
| Parameter | Required | What it does |
|---|---|---|
domain |
yes | The name to look up. A full URL works too, the host is pulled out of it. |
type |
no | One of A, AAAA, CNAME, MX, NS, TXT, SOA, SRV, CAA, PTR. Upper or lower case. |
Leave the type out
Without type you don't get A records only. You get the six common types in one call: A, AAAA, MX, TXT, NS and CNAME. They're looked up at the same time, so it isn't slower than asking for one.
curl -G "https://apixies.io/api/v1/dns-lookup" \
-H "X-API-Key: YOUR_API_KEY" \
--data-urlencode "domain=github.com"
I got ten records back: one A, one MX (github-com.mail.protection.outlook.com, so their mail runs on Microsoft) and eight NS records split between NS1 and AWS.
That's one request against your daily limit, not six. If you want the overall picture of a domain, this is the cheap way to get it.
Look at partial when you do this. If one of the six lookups didn't come back in time, you still get the rest, partial is true, and timed_out tells you which type is missing:
"timed_out": { "A": false, "AAAA": false, "MX": false, "TXT": true, "NS": false, "CNAME": false },
"partial": true
An empty list for a type and a timed out type aren't the same thing. Check before you decide a domain has no TXT records.
What a record looks like
Every record has type, host and ttl. The rest depends on the type, and it's worth knowing before you write code against it, because the value isn't always in the same field.
| Type | Extra fields |
|---|---|
| A | ip |
| AAAA | ipv6 |
| CNAME, NS, PTR | target |
| MX | priority, target |
| TXT | txt |
| SOA | mname, rname, serial, refresh, retry, expire, minimum_ttl |
| SRV | priority, weight, port, target |
| CAA | flags, tag, value |
Mail servers for gmail.com:
"records": [
{ "type": "MX", "host": "gmail.com", "ttl": 2648, "priority": 5, "target": "gmail-smtp-in.l.google.com" },
{ "type": "MX", "host": "gmail.com", "ttl": 2648, "priority": 10, "target": "alt1.gmail-smtp-in.l.google.com" },
{ "type": "MX", "host": "gmail.com", "ttl": 2648, "priority": 20, "target": "alt2.gmail-smtp-in.l.google.com" },
{ "type": "MX", "host": "gmail.com", "ttl": 2648, "priority": 30, "target": "alt3.gmail-smtp-in.l.google.com" },
{ "type": "MX", "host": "gmail.com", "ttl": 2648, "priority": 40, "target": "alt4.gmail-smtp-in.l.google.com" }
]
The ttl is what's left on the answer in the resolver's cache, in seconds. So it counts down between calls, and a small number doesn't mean the domain owner set a short TTL.
Things that'll trip you up
Only MX records are sorted. They come back by priority, lowest first, the order mail is tried in. Everything else is in whatever order the resolver gave, and DNS doesn't promise one. Don't build on records[0] for A or NS records.
An A lookup can return a CNAME. www.github.com is an alias, so asking for its A record gives you both steps:
"records": [
{ "type": "CNAME", "host": "www.github.com", "ttl": 1025, "target": "github.com" },
{ "type": "A", "host": "github.com", "ttl": 32, "ip": "140.82.121.4" }
]
If your code does records[0].ip, it breaks here. Filter by type first.
A domain that doesn't exist is still a 200. I looked up this-domain-does-not-exist-zz81.com and got "status": "success" with "record_count": 0. No records is an answer, not an error. What tells the two cases apart is exists. It's false when the resolver says the name isn't there at all, and true for a real domain that just has no records of that type.
Some records live on underscore names. DMARC is a TXT record on _dmarc.gmail.com, a DKIM key sits on selector._domainkey.example.com, and SRV names look like _imaps._tcp.gmail.com. Look those names up directly. If it's mail setup you're after, the email authentication endpoint finds and reads SPF, DKIM and DMARC in one call.
A 504 means try again. The code is UPSTREAM_TIMEOUT: the resolver didn't answer in time. Retry once before you give up on the domain.
In code
One small function per language. Each returns the list of records and throws on anything that isn't a success.
JavaScript
async function dnsLookup(domain, type) {
const params = new URLSearchParams(type ? { domain, type } : { domain });
const res = await fetch(`https://apixies.io/api/v1/dns-lookup?${params}`, {
headers: { "X-API-Key": process.env.APIXIES_API_KEY },
});
const body = await res.json();
if (body.status !== "success") {
throw new Error(`${body.code}: ${body.message}`);
}
return body.data.records;
}
const mx = await dnsLookup("gmail.com", "MX");
console.log(mx[0].target); // gmail-smtp-in.l.google.com
Python
import os
import requests
def dns_lookup(domain, record_type=None):
params = {"domain": domain}
if record_type:
params["type"] = record_type
res = requests.get(
"https://apixies.io/api/v1/dns-lookup",
params=params,
headers={"X-API-Key": os.environ["APIXIES_API_KEY"]},
timeout=15,
)
body = res.json()
if body["status"] != "success":
raise RuntimeError(f"{body['code']}: {body['message']}")
return body["data"]["records"]
for record in dns_lookup("gmail.com", "TXT"):
print(record["txt"])
PHP
function dnsLookup(string $domain, ?string $type = null): array
{
$query = http_build_query(array_filter(['domain' => $domain, 'type' => $type]));
$context = stream_context_create(['http' => [
'header' => 'X-API-Key: ' . getenv('APIXIES_API_KEY'),
'ignore_errors' => true,
]]);
$body = json_decode(file_get_contents("https://apixies.io/api/v1/dns-lookup?$query", false, $context), true);
if (($body['status'] ?? '') !== 'success') {
throw new RuntimeException(($body['code'] ?? 'ERROR') . ': ' . ($body['message'] ?? 'no response'));
}
return $body['data']['records'];
}
$ips = array_column(
array_filter(dnsLookup('github.com', 'A'), fn ($r) => $r['type'] === 'A'),
'ip'
);
print_r($ips);
A real use: did the customer point their domain at you?
If you host things on customer domains (status pages, shops, docs), you need to know when their DNS is ready. That's one CNAME lookup. GitHub's status page is a customer of Statuspage, and it shows:
curl -G "https://apixies.io/api/v1/dns-lookup" \
-H "X-API-Key: YOUR_API_KEY" \
--data-urlencode "domain=www.githubstatus.com" \
--data-urlencode "type=CNAME"
"records": [
{ "type": "CNAME", "host": "www.githubstatus.com", "ttl": 870, "target": "kctbh9vrtdwd.stspg-customer.com" }
]
Your version of that check:
async function pointsAtUs(customerDomain) {
const records = await dnsLookup(customerDomain, "CNAME");
return records.some((r) => r.target.endsWith(".your-platform.example"));
}
Run it when the customer clicks "verify", not on a timer for every customer. The free tier is 75 requests a day. That's plenty for checks a person triggers, and not much for polling.
One thing this can't tell you is whether a change has reached the rest of the world yet. The lookup goes through one resolver. For that there's the DNS propagation endpoint, which asks several public resolvers and compares.
Next steps
- DNS Lookup API reference: parameters, fields and error codes
- DNS Lookup tool: try a domain in the browser, no key needed
- DNS record types explained: what each type is for, with real answers
- Check MX records for email deliverability: catch domains that can't get mail
- All guides