DNS Robot API
The engines behind our DNS, domain, email and network tools as a JSON API. Every lookup is one GET request: no key, no sign-up, and CORS open to any website.
- No API key
- CORS on every origin
- OpenAPI 3.1
{
"query": {
"domain": "github.com"
},
"verdict": "mail",
"zone": "github.com",
"answer": {
"source": "authoritative",
"server": "ns-421.awsdns-52.com",
"ip": "205.251.193.165",
"viaCloudflare": false,
"status": "ok",
"authoritative": true,
"dnssecValidated": null,
"negativeTtl": null,
"cnameChain": []
},
"mx": [
{
"priority": 0,
"host": "github-com.mail.protection.outlook.com",
"ttl": 300,
"provider": {
"id": "microsoft-365",
"name": "Microsoft 365",
"kind": "mailbox"
},
"status": "ok",
"cname": null,
"addresses": [
{
"ip": "52.101.10.16",
"network": {
"asn": 8075,
"name": "Microsoft",
"prefix": "52.96.0.0/12",
"country": "US"
},
"reverse": {
"name": "mail-bn6pr21cu00500.inbound.protection.outlook.com",
"forwardConfirmed": true
}
},
{
"ip": "52.101.9.26",
"network": {
"asn": 8075,
"name": "Microsoft",
"prefix": "52.96.0.0/12",
"country": "US"
},
"reverse": null
},
{
"ip": "52.101.194.164",
"network": {
"asn": 8075,
"name": "Microsoft",
"prefix": "52.96.0.0/12",
"country": "US"
},
"reverse": null
},
{
"ip": "52.101.194.3",
"network": {
"asn": 8075,
"name": "Microsoft",
"prefix": "52.96.0.0/12",
"country": "US"
},
"reverse": null
},
{
"ip": "2a01:111:f403:c902::7",
"network": {
"asn": 8075,
"name": "Microsoft",
"prefix": "2a01:111:f000::/36",
"country": "GB"
},
"reverse": {
"name": "mail-sj0pr02cu00107.inbound.protection.outlook.com",
"forwardConfirmed": true
}
},
{
"ip": "2a01:111:f403:c922::7",
"network": {
"asn": 8075,
"name": "Microsoft",
"prefix": "2a01:111:f000::/36",
"country": "GB"
},
"reverse": null
},
{
"ip": "2a01:111:f403:f804::2",
"network": {
"asn": 8075,
"name": "Microsoft",
"prefix": "2a01:111:f000::/36",
"country": "GB"
},
"reverse": null
},
{
"ip": "2a01:111:f403:f909::6",
"network": {
"asn": 8075,
"name": "Microsoft",
"prefix": "2a01:111:f000::/36",
"country": "GB"
},
"reverse": null
}
],
"dnssecSigned": false
}
],
"implicitMx": null,
"parentZone": null,
"provider": {
"mailbox": {
"id": "microsoft-365",
"name": "Microsoft 365",
"kind": "mailbox",
"evidence": [
{
"kind": "mx",
"value": "github-com.mail.protection.outlook.com"
},
{
"kind": "spf",
"value": "include:spf.protection.outlook.com"
},
{
"kind": "autodiscover",
"value": "autodiscover.outlook.com"
}
]
},
"gateway": null,
"ownServers": false,
"multipleProviders": []
},
"security": {
"spf": {
"record": "v=spf1 ip4:192.30.252.0/22 include:spf.protection.outlook.com include:_netblocks.google.com include:_netblocks2.google.com include:mail.zendesk.com include:_spf.salesforce.com include:servers.mcsv.net include:mktomail.com include:sendgrid.net ip4:62.253.227.114 ip4:166.78.69.169 ip4:166.78.69.170 ip4:166.78.71.131 ~all",
"lookups": 10,
"all": "~all",
"multipleRecords": false,
"missingIncludes": []
},
"dmarc": {
"record": "v=DMARC1; p=quarantine; sp=reject; pct=100; rua=mailto:dmarc@github.com; ruf=mailto:dmarc@github.com; fo=1",
"policy": "quarantine",
"subdomainPolicy": "reject",
"effectivePolicy": "quarantine",
"pct": 100,
"rua": [
"dmarc@github.com"
],
"inheritedFrom": null,
"multipleRecords": false
},
"mtaSts": null,
"tlsRpt": null,
"bimi": null,
"dane": [
{
"host": "github-com.mail.protection.outlook.com",
"tlsa": [],
"dnssecValidated": false,
"status": "none"
}
],
"dnssec": "unsigned",
"ds": [],
"autodiscover": "autodiscover.outlook.com"
},
"sources": [
{
"source": "authoritative",
"server": "ns-421.awsdns-52.com",
"ip": "205.251.193.165",
"status": "ok",
"viaCloudflare": false,
"records": [
{
"priority": 0,
"host": "github-com.mail.protection.outlook.com",
"ttl": 300
}
],
"tookMs": 27,
"sameAsAuthoritative": null
},
{
"source": "google",
"server": "dns.google",
"ip": "8.8.8.8",
"status": "ok",
"viaCloudflare": false,
"records": [
{
"priority": 0,
"host": "github-com.mail.protection.outlook.com",
"ttl": 205
}
],
"tookMs": 82,
"sameAsAuthoritative": true
},
{
"source": "cloudflare",
"server": "one.one.one.one",
"ip": "1.1.1.1",
"status": "ok",
"viaCloudflare": false,
"records": [
{
"priority": 0,
"host": "github-com.mail.protection.outlook.com",
"ttl": 300
}
],
"tookMs": 2,
"sameAsAuthoritative": true
}
],
"issues": [],
"tookMs": 1039
}Quick start
Ask for github.com's mail servers. There is nothing to install and nothing to sign up for:
Every answer is JSON with the normalised query you asked about, the result, and tookMs. The same call works from a browser page, a shell script, a serverless function or a CI job.
curl "https://dnsrobot.net/api/v2/mx?domain=github.com"const params = new URLSearchParams({ domain: "github.com" });
const res = await fetch(`https://dnsrobot.net/api/v2/mx?${params}`);
const data = await res.json();
if (!res.ok) throw new Error(data.error.message);
console.log(data.mx);import requests
r = requests.get(
"https://dnsrobot.net/api/v2/mx",
params={"domain": "github.com"},
timeout=30,
)
data = r.json()
if not r.ok:
raise RuntimeError(data["error"]["message"])
print(data["mx"])package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
)
func main() {
q := url.Values{"domain": {"github.com"}}
res, err := http.Get("https://dnsrobot.net/api/v2/mx?" + q.Encode())
if err != nil {
panic(err)
}
defer res.Body.Close()
var data map[string]any
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
panic(err)
}
if res.StatusCode != http.StatusOK {
panic(data["error"])
}
fmt.Println(data["mx"])
}<?php
$query = http_build_query(["domain" => "github.com"]);
$context = stream_context_create(["http" => ["ignore_errors" => true, "timeout" => 30]]);
$data = json_decode(file_get_contents("https://dnsrobot.net/api/v2/mx?" . $query, false, $context), true);
if (isset($data["error"])) {
throw new RuntimeException($data["error"]["message"]);
}
print_r($data["mx"]);require "net/http"
require "json"
uri = URI("https://dnsrobot.net/api/v2/mx")
uri.query = URI.encode_www_form(domain: "github.com")
data = JSON.parse(Net::HTTP.get(uri))
raise data["error"]["message"] if data["error"]
puts data["mx"]// Cargo.toml: reqwest = { version = "0.13", features = ["blocking", "json"] }, serde_json = "1"
fn main() -> Result<(), Box<dyn std::error::Error>> {
let res = reqwest::blocking::get("https://dnsrobot.net/api/v2/mx?domain=github.com")?;
let ok = res.status().is_success();
let data: serde_json::Value = res.json()?;
if !ok {
return Err(data["error"]["message"].to_string().into());
}
println!("{}", data["mx"]);
Ok(())
}using System.Text.Json;
using var http = new HttpClient();
var res = await http.GetAsync("https://dnsrobot.net/api/v2/mx?domain=github.com");
var data = JsonDocument.Parse(await res.Content.ReadAsStringAsync()).RootElement;
if (!res.IsSuccessStatusCode)
throw new Exception(data.GetProperty("error").GetProperty("message").GetString());
Console.WriteLine(data.GetProperty("mx"));Requests and responses
- Base URL
https://dnsrobot.net/api/v2. Every endpoint is a GET with query parameters, andGET /api/v2lists them all.- Input
- Parameters are read the way our tool pages read them: a full URL, upper case, a trailing dot or an internationalised domain all work, and the normalised value comes back in
query. - Output
- JSON with camelCase keys. Durations end in
Ms, DNS TTLs are seconds inttl, dates are ISO 8601 in UTC. A value we couldn't determine isnull, never a made-up0orfalse. - Findings are not errors
- A domain that doesn't exist, a closed port or an address on a blocklist is a
200answer with that finding in the body. Problems in a record come back inissues, each with aseverity, a stablecodeand a plain-Englishmessage. - Partial answers
- When one source fails or times out, the answer still comes back with what the others found, plus
incomplete: trueandwarningssaying what is missing. - Versioning
- New fields and new issue codes can appear at any time, so ignore keys you don't know. Renaming or removing a field would mean a new version at
/api/v3.
Errors
An error is about the request or our side, never about the target. Its body always has the same shape, and code is stable for your code to branch on:
| Status | Code | When |
|---|---|---|
400 | missing_parameter | A required parameter is absent or empty; param names it. |
400 | invalid_parameter | A parameter can't be read; param names it. |
404 | not_found | No such endpoint. |
405 | method_not_allowed | The method isn't GET or OPTIONS. |
422 | target_not_allowed | A private, reserved or refused target (RFC 1918, loopback, documentation ranges). |
429 | rate_limited | Over the endpoint's limit a minute; wait Retry-After seconds. |
500 | internal_error | A bug on our side. Retrying rarely helps; tell us the requestId. |
503 | unavailable | Every source the lookup needs failed. Retry after a short wait. |
504 | timeout | The lookup ran out of time before any source answered. |
{
"error": {
"status": 400,
"code": "invalid_parameter",
"message": "domain must be a domain name like example.com.",
"param": "domain",
"requestId": "8d2f6c41a9e3b7f0",
"docs": "https://dnsrobot.net/api-docs#errors"
}
}Rate limits and fair use
Limits count calls a minute per IP address (an IPv6 caller per /64, or per /56 where noted). Every answer carries RateLimit-Policy: <calls>;w=60; past the limit you get a 429 with Retry-After. Our tool pages have their own budget, so using the API never slows them down for you.
| Endpoint | Calls a minute | Answers kept |
|---|---|---|
DNS lookup /api/v2/dns | 60 | Not kept |
DNS propagation /api/v2/dns/propagation | 20 | Not kept |
MX lookup /api/v2/mx | 20 | Kept for the MX records' TTL, 30 seconds to 5 minutes (30 seconds when a part failed), shared with the MX Lookup page. A kept answer carries an `Age` header, and the resolvers' TTLs are counted down. |
NS lookup /api/v2/ns | 20 | Kept for the shortest NS TTL seen, 30 seconds to 5 minutes (30 seconds when a part failed), shared with the NS Lookup page. A kept answer carries an `Age` header. |
CNAME lookup /api/v2/cname | 20 | Kept for the chain's lowest TTL, 30 seconds to 5 minutes (30 seconds when a part failed), shared with the CNAME Lookup page. A kept answer carries an `Age` header, and the resolvers' TTLs are counted down. |
Reverse DNS /api/v2/reverse-dns | 20 | Kept for the PTR record's TTL, 30 seconds to 5 minutes (30 seconds when a part failed), shared with the Reverse DNS page. A kept answer carries an `Age` header, and the resolvers' TTLs are counted down. |
WHOIS lookup /api/v2/whois | 20 | A found record is kept an hour and a not-registered answer ten minutes, shared with the WHOIS Lookup page; `Cache-Control: max-age` says how long this answer has left. |
Hosting check /api/v2/hosting | 10 | Kept an hour (a minute when a source didn't answer), shared with the Hosting Checker page; `Cache-Control: max-age` says how long this answer has left. |
Domain to IP /api/v2/domain-ip | 30 | Not kept |
SPF check /api/v2/spf | 20 | Not kept |
DKIM check /api/v2/dkim | 20 | Not kept |
DMARC check /api/v2/dmarc | 20 | Not kept |
BIMI check /api/v2/bimi | 20 | Not kept |
SSL checker /api/v2/ssl | 10 | A finished check is kept 10 minutes (1 minute when a part didn't finish or the server didn't answer), shared with the SSL Checker page; `checkedAt` says when it ran. Answers for names on Cloudflare's network aren't kept. |
HTTP security headers /api/v2/http-headers | 20 (/56) | The same run serves both endpoints and the HTTP Header Checker and Redirect Checker pages: a finished check is kept 2 minutes (30 seconds when a part ran out of time) per URL and user agent. A run that ended on a bot wall or whose first URL got no answer isn't kept. |
Redirect chain /api/v2/redirects | 20 (/56) | The same run serves both endpoints and the HTTP Header Checker and Redirect Checker pages: a finished check is kept 2 minutes (30 seconds when a part ran out of time) per URL and user agent. A run that ended on a bot wall or whose first URL got no answer isn't kept. |
IP lookup /api/v2/ip | 60 | Not kept |
What is my IP /api/v2/me | 60 | Not kept |
ASN lookup /api/v2/asn | 30 | 1 hour (`public, max-age=3600`): the tables change with each data refresh, Team Cymru's routing every 4 hours. Answers with `incomplete` aren't cached. |
Port check /api/v2/port | 12 | Not kept |
IP blacklist check /api/v2/blacklist | 20 | 10 minutes, 1 minute when a list refused or didn't answer (`public, max-age` is the time left). |
MAC lookup /api/v2/mac | 120 | 1 day (`public, max-age=86400`): the registry snapshot changes with our monthly sync. |
MAC vendor search /api/v2/mac/vendors | 120 | 1 day (`public, max-age=86400`): the registry snapshot changes with our monthly sync. |
User agent parser /api/v2/user-agent | 120 (/56) | Not kept |
The API is free and comes without an uptime guarantee. Cache answers for their TTL, back off on a 429, and don't use it to scan networks you don't run. If you build something public on it, a link to dnsrobot.net is appreciated. Use is subject to the terms of service.
Calling it from a browser
Every endpoint sends Access-Control-Allow-Origin: *, so a page on any website can call it with fetch() and read X-Request-Id, RateLimit-Policy and Retry-After. No cookies or credentials are read, and nothing you look up is stored beyond the short answer caches listed above.
OpenAPI and client generators
The whole API is described by an OpenAPI 3.1 document at /api/v2/openapi.json, with every parameter, response field and error. Import it into Postman, Insomnia or Bruno, or generate a typed client with openapi-generator or Kiota.
SDKs and command-line tool
Official clients wrap the API in your language's idioms. They call the original API today; releases for v2 are being prepared, and the raw HTTP samples on this page work in the meantime.
- Python
pip install dnsrobotSource (Python) - Go
go get github.com/dnsrobot/dnsrobot-goSource (Go) - PHP
composer require dnsrobot/dnsrobotSource (PHP) - Ruby
gem install dnsrobotSource (Ruby) - .NET
dotnet add package DnsRobotSource (.NET) - Deno / TypeScript
deno add jsr:@dnsrobot/clientSource (Deno / TypeScript) - Dart
dart pub add dnsrobotSource (Dart) - PowerShell
Install-Module -Name DNSRobotSource (PowerShell) - Command line
npm i -g dns-robot-cliSource (Command line)
Endpoints
Every endpoint runs on the same engine as one of our tools. Each page has its parameters, every response field, code in eight languages and real recorded answers.
DNS
Records from any resolver, propagation across 30+ resolvers, MX, NS, CNAME chains and reverse DNS.
- DNS lookupGET
/api/v2/dnsAsk one resolver one question: any record type of a name, or the PTR of an IP address. - DNS propagationGET
/api/v2/dns/propagationAsk 34 public resolvers around the world for a record at once and see whether they agree. - MX lookupGET
/api/v2/mxA domain's mail servers in delivery order, who runs them, their addresses, and the mail security records. - NS lookupGET
/api/v2/nsA domain's nameservers from the registry and from the zone, each one asked directly: lame, glue, serials, DNSSEC. - CNAME lookupGET
/api/v2/cnameFollow a name's CNAME chain hop by hop to its addresses, with the platform behind it and dangling or looping targets. - Reverse DNSGET
/api/v2/reverse-dnsAn IP address's PTR names, whether they point back (FCrDNS), who runs its reverse zone, and the mail providers' PTR rules.
Domains
Registration data, the host behind a site and every address a domain points to.
- WHOIS lookupGET
/api/v2/whoisRegistration data for a domain, an IP address or an AS number, from its registry's RDAP server or WHOIS. - Hosting checkGET
/api/v2/hostingWho hosts a website: the host and its evidence, the CDN in front, DNS and email providers, registrar, certificate CA and abuse contact. - Domain to IPGET
/api/v2/domain-ipEvery IPv4 and IPv6 address a name resolves to, with each address's network, reverse name, CDN and city, the CNAME chain and five resolvers' answers.
Email security
SPF, DKIM, DMARC and BIMI records, parsed and checked against their RFCs.
- SPF checkGET
/api/v2/spfA domain's SPF record walked like a receiver walks it: the lookup bill against RFC 7208's limit of 10, void lookups, every include expanded, the senders it authorises, and with `ip` the check_host result for that address. - DKIM checkGET
/api/v2/dkimA domain's DKIM public keys: the selectors you name, or a sweep of about 140 provider and common selectors, each key record parsed with its type, size, flags and findings. - DMARC checkGET
/api/v2/dmarcThe DMARC policy receivers apply to a domain: the record found (or inherited), every tag's effective value, where reports go and whether those addresses accept them, and how strong the policy is. - BIMI checkGET
/api/v2/bimiWhether inboxes show a domain's logo: the BIMI record, the DMARC policy it needs, the SVG checked against SVG Tiny PS, the VMC or CMC with its chain, and what Gmail, Apple Mail, Yahoo and others would do.
Web & SSL
Certificates and TLS, HTTP security headers and redirect chains.
- SSL checkerGET
/api/v2/sslCheck a server's TLS certificate, chain, trust in four root stores, revocation, protocols and ciphers, and grade it A+ to F. - HTTP security headersGET
/api/v2/http-headersFetch a URL like a browser, follow its redirects, and grade the final answer's security headers with Mozilla HTTP Observatory's tests. - Redirect chainGET
/api/v2/redirectsFollow a URL's redirects hop by hop (HTTP 3xx, meta refresh and Refresh headers) and judge the chain the way Google and browsers do.
IP & network
IP and ASN details, your own connection, open ports, blocklists and MAC vendors.
- IP lookupGET
/api/v2/ipWho an IPv4 or IPv6 address belongs to: its network, prefix, registry, reverse DNS name, location and connection type. - What is my IPGET
/api/v2/meYour own public IP address, network and approximate location, as Cloudflare saw your request. - ASN lookupGET
/api/v2/asnAn autonomous system's holder, registry, prefixes, address space, peers and users, by AS number or from an IP, prefix or domain. - Port checkGET
/api/v2/portWhether TCP ports on a host are open, closed or filtered, checked from Cloudflare's edge, with each open port's banner. - IP blacklist checkGET
/api/v2/blacklistWhether an IPv4 or IPv6 address is on 28 DNS blocklists (DNSBLs), with each list's answer, its delisting link and a verdict. - MAC lookupGET
/api/v2/macThe vendor behind a MAC address or prefix, its IEEE block, and what the address itself says (randomized, multicast, virtual). - MAC vendor searchGET
/api/v2/mac/vendorsFind a vendor's MAC address prefixes by its name. - User agent parserGET
/api/v2/user-agentBrowser, engine, operating system, device and crawler from a User-Agent string, telling real values from frozen ones.
The original API
The first DNS Robot API (POST requests to /api/<tool>, the one the published SDK versions call) keeps answering with the same request and response shapes. It gets no new features: move to the v2 endpoint beside each one.
| Original endpoint | Use instead |
|---|---|
POST /api/dns-query | GET /api/v2/dns |
POST /api/whois | GET /api/v2/whois |
POST /api/ssl-certificate | GET /api/v2/ssl |
POST /api/spf-checker | GET /api/v2/spf |
POST /api/dkim-checker | GET /api/v2/dkim |
POST /api/dmarc-checker | GET /api/v2/dmarc |
POST /api/mx-lookup | GET /api/v2/mx |
POST /api/ns-lookup | GET /api/v2/ns |
POST /api/ip-info | GET /api/v2/ip |
POST /api/http-headers | GET /api/v2/http-headers |
GET /api/port-check | GET /api/v2/port |
POST /api/cname-lookup | GET /api/v2/cname |
POST /api/reverse-dns | GET /api/v2/reverse-dns |
POST /api/domain-ip | GET /api/v2/domain-ip |
POST /api/bimi-checker | GET /api/v2/bimi |
GET /api/ip-blacklist | GET /api/v2/blacklist |
Changelog
- API v2: GET endpoints on the tools' current engines, one error format, rate-limit headers and an OpenAPI 3.1 document.
Questions about the API
Is the DNS Robot API free?
Do I need an API key?
Is there a free WHOIS API?
GET /api/v2/whois?q=example.com returns a domain's registrar, creation and expiry dates, status codes, nameservers and DNSSEC as JSON. It asks the registry's RDAP service first and falls back to port-43 WHOIS for the extensions without RDAP, so country-code domains work too. The same parameter takes an IP address or an AS number.Is there a free IP lookup API?
GET /api/v2/ip?ip=8.8.8.8 returns the network that announces an address, its AS number and holder, the registry, the reverse DNS name and an approximate location from the free DB-IP database. GET /api/v2/me answers the same for the caller's own address, and ?format=text returns just the IP for shell scripts.Which DNS resolver does the DNS lookup API use?
resolver=cloudflare, quad9, opendns, adguard or any public IPv4 resolver to ask that one instead. For the same record across more than 30 resolvers worldwide, use the propagation endpoint, which is the engine behind our DNS propagation checker.Can I call the API from JavaScript in the browser?
Access-Control-Allow-Origin: *, so fetch() works from a page on any website without a proxy. Remember that limits count per visitor IP, which suits a tool where each visitor runs their own lookups. For many lookups from one server, cache answers for their TTL.What happens when I hit the rate limit?
rate_limited and a Retry-After header in seconds. Wait that long and send the request again; the limits reset every minute. Each endpoint's limit is in the table above and in the RateLimit-Policy header of every answer, so a client can pace itself before it ever sees a 429.