Querying every nameserver directly — this takes a few seconds.
Querying every nameserver directly — this takes a few seconds.
DNS, mail and hosting reports as JSON. The same engine as the web interface, versioned under /api/v1 — a keyed request and a browser request measure exactly the same things and cost exactly the same.
No key, no sign-up, no preamble. One request returns the whole report:
curl -s https://inspectmydns.com/api/v1/domain/example.com
It answers when the scan is finished. There is no job to poll and no pagination — the response is the report.
Every endpoint is also described in the OpenAPI 3.1 spec, which is the machine-readable version of this page.
Optional. Anonymous access works at the same limits a browser gets, which is enough for interactive use and for a small monitoring job.
curl -H "Authorization: Bearer imd_a1b2c3d4_…" \ https://inspectmydns.com/api/v1/domain/example.com
A key does two things: it raises the hourly ceiling, and it makes the rate-limit identity the key rather than your IP address — which matters when your team shares an outbound address, or when you run from a CI runner whose address you do not control.
| Tier | Requests/hour |
|---|---|
| anonymous | 30 |
| free | 100 |
| standard | 1,000 |
| high | 10,000 |
Create a key with an email address — no password. Only sha256(key) is stored, so a key is shown once at creation and cannot be recovered afterwards; losing it means creating a new one.
The report. Served from cache when a scan completed within the last 5 minutes, otherwise scanned fresh.
curl -s https://inspectmydns.com/api/v1/domain/example.com | jq '{score, cached, counts}'{
"score": 91,
"cached": true,
"counts": { "pass": 30, "info": 6, "warn": 2, "fail": 0, "unknown": 1 }
}Concurrent requests for the same domain wait on the scan already running rather than starting a second one, so repeated calls do not multiply the load on that domain’s nameservers.
Force a fresh scan, ignoring the cache.
curl -X POST -H "Authorization: Bearer $INSPECTMYDNS_KEY" \ https://inspectmydns.com/api/v1/domain/example.com/refresh
Add ?unlisted=1 to keep the result off the home page’s recent list and out of the sitemap. That is a publication choice, not privacy: the report stays readable by anyone holding its URL.
Scans where the significant records actually changed, newest first.
curl -s https://inspectmydns.com/api/v1/domain/example.com/history \
| jq '.entries[] | {scannedAt, changes}'{
"scannedAt": "2026-03-02T09:14:22.000Z",
"changes": ["MX moved from Google Workspace to Microsoft 365"]
}One specific historical scan, immutable.
Stable within v1. Fields may be added; none are removed or retyped.
{
"domain": "example.com",
"scannedAt": "2026-08-14T15:36:00.000Z",
"cached": false,
"ageSeconds": 0,
"shareUrl": "https://inspectmydns.com/s/kWpmpKnwcPY3",
"score": 91,
"counts": { "pass": 30, "info": 6, "warn": 2, "fail": 0, "unknown": 1 },
"providers": [
{ "provider": "AWS Route 53", "category": "dns", "confidence": "high" }
],
"registrar": { "name": "…", "abuseEmail": "…", "statuses": [] },
"reputation": { "enabled": true, "listed": false, "partial": false },
"checks": [
{
"id": "mailauth.spf-lookups",
"category": "mail-auth",
"title": "SPF stays within the 10-lookup limit",
"severity": "warn",
"summary": "SPF costs 9 of the 10 permitted DNS lookups.",
"why": "Past 10 lookups receivers return permerror and SPF stops working."
}
]
}Every check has a stable id that is never renamed, so you can alert on specific ones. All of them are listed with the reasoning behind each.
severity: "unknown" is not a failure. It means we could not measure the check — most often because outbound port 25 is blocked on the machine running the scan. It says nothing about the domain and it does not affect score. Treating it as a failure in your alerting will page you about our network, not yours.reputation.partial: true means not measured. At least one blacklist refused to answer — Spamhaus and others reject queries arriving via public resolvers and enforce daily quotas. With it set, listed: false means we do not know, not clean.cached: true comes with ageSeconds. If a record was changed a minute ago, that number is why the change is not visible yet. It is a reason to wait, not a reason to call /refresh in a loop.Three counters. The first two are yours, keyed on a key id or on a salted daily hash of your network block — the raw address is never stored.
| Counter | Limit | Window | Keyed on |
|---|---|---|---|
| Fresh checks per domain | 3 | 15 minutes | you |
| Fresh checks across all domains | 30 anonymous, or your tier | 60 minutes | you |
| Fresh scans of one domain | 120 | 60 minutes | the domain |
Checking many different domains is normal use and the first counter does not touch it — it exists to catch repeated checks of one domain. The third is not about you at all, and no key raises it: scanning means issuing DNS queries and probing mail servers that belong to somebody who did not ask us to, and that ceiling holds however the requests are spread across callers.
Cached responses consume nothing. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, and a 429 names which counter tripped in its scope field — so you know whether to back off on one domain, back off entirely, or simply wait because the domain is busy and it was never your budget. It includes the most recent stored report either way, under stale.
| Status | error | Meaning |
|---|---|---|
| 400 | invalid_domain | Not a plausible registrable name |
| 403 | domain_blocked | The domain’s owner opted out of being scanned |
| 404 | not_found | No scan with that share id |
| 429 | rate_limited | See the scope field |
| 502 | scan_failed | The scan itself errored — ours, not the domain’s |
One request returns one complete report — no job to poll, no pagination — so this is a straightforward API for an agent to drive. What an agent needs telling is the shape of the answer rather than its format: that a severity: "unknown" is our measurement failing and not the domain’s, that a scan sends live queries to somebody else’s nameservers, and that a 429 already carries the report it is being told to wait for.
Paste this into a system prompt, a CLAUDE.md, a skill, or the top of a task description. It works with or without a key.
Not required, and worth having if you are running more than a handful of checks an hour or calling from an address you share with other people. Create one at /keys.