who.is Domain Intelligence API

Get parsed WHOIS records, normalized RDAP data, DNS records, and TLS certificate details for any domain, all as clean JSON over HTTPS. Because we keep the history, you can also see what changed and when, from a domain moving to a new registrar to a swapped set of nameservers or a fresh certificate. Zone feeds let you watch a whole TLD day by day and catch new registrations, nameserver moves, and drops as they happen. Start free with no card, and build anything from a quick lookup tool to a brand monitor to a research pipeline over millions of domains.

Endpoints at a glance

Quickstart

Create a key on the API keys page. Keys are shown once at creation — store yours securely. Send the key with every request as a Bearer token in the Authorization header: Authorization: Bearer wis_live_<key>. Never put the key in a query string. A missing or invalid key returns 401 unauthorized.

curl

curl https://api.who.is/v1/whois/example.com \
  -H "Authorization: Bearer wis_live_..."

Python

Uses the requests library (pip install requests).

import requests

response = requests.get(
    "https://api.who.is/v1/whois/example.com",
    headers={"Authorization": "Bearer wis_live_..."},
)
response.raise_for_status()
print(response.json())

MCP server

The API is also available to AI clients over the Model Context Protocol. Point your client at https://api.who.is/v1/mcp and authenticate with the same API key, sent as an Authorization: Bearer header.

{
  "url": "https://api.who.is/v1/mcp",
  "headers": { "Authorization": "Bearer wis_live_..." }
}

Tools: whois, rdap, dns, dns_changes, certificates, certificates_changes, domain_history, domain_history_snapshots, nameservers, nameserver, nameserver_domains, what_changed. Each tool spends the same credits as the endpoint it calls; the full history tool spends 200 credits once per domain and its retries are free.

Credits & plans

Every API call costs credits: 1 for a lookup, change timeline, or zone-feed page; 2 for a live refresh; 200 for a full-history query. Not-found responses are free. Your plan grants a fresh allowance of credits each calendar month (UTC). Credits you buy ($4.99 per 2,000, any quantity) never expire and are used after your allowance. The free allowance covers cached lookups only; paid endpoints use a paid plan's allowance or purchased credits.

What a call costs

CallCredits
Lookup (WHOIS, RDAP, DNS, certificate, history summary, nameserver)1
DNS or certificate change timeline1
Live upstream refresh (?live=true)2
Zone-event feed page1
Full domain history (per domain, per archive generation)200

Plans

PlanPriceCredits / monthRequests / secondLive refreshes / dayPaid endpoints
Free$050010No
Pro API Access$29.00/mo15,00051,000Yes
Growth API Access$99.00/mo60,00052,000Yes
Scale API Access$199.00/mo150,00054,000Yes

Credit packs: $4.99 per 2,000 credits — buy any quantity from your API keys page. DNS and certificate change timelines (/changes) are paid endpoints — current-state DNS and certificate lookups are on every plan; on Free without purchased credits the timelines return 403 plan_required.

Endpoints

GET/v1/whois/{domain}

Snapshot-first parsed WHOIS: registrar, statuses, events, nameservers, and redacted-as-received contacts. Serves a stored snapshot (≤30 days) unless `?live=true` forces an upstream refresh. Raw upstream text is never returned.

Parameters

NameInRequiredDescription
domainpathyesThe domain to look up, e.g. `example.com`. A subdomain is resolved to its registrable domain (`www.example.com` → `example.com`).
livequerynoSet to `true` to force a fresh upstream lookup (bounded by a 1-hour floor). A paid endpoint costing 2 credits — the free allowance cannot pay for it, so a free key with no purchased credits is denied with `plan_required`. A daily live refresh limit also applies. Any other value is a normal snapshot lookup.

Example response

{
  "domain": "example.com",
  "registrar": "Example Registrar, Inc.",
  "whois_server": "whois.example-registrar.com",
  "referral_url": "https://example-registrar.com",
  "statuses": [
    "clientTransferProhibited"
  ],
  "snapshot_time": "2026-07-30T12:00:00.000Z",
  "events": [
    {
      "event_action": "registration",
      "event_date": "2003-01-01T00:00:00.000Z"
    },
    {
      "event_action": "expiration",
      "event_date": "2027-01-01T00:00:00.000Z"
    }
  ],
  "nameservers": [
    {
      "ldh_name": "ns1.example.com"
    },
    {
      "ldh_name": "ns2.example.com"
    }
  ],
  "contacts": {
    "registrant": {
      "organization": "Acme",
      "state_province": "CA",
      "country": "US",
      "email": "jane@acme.test"
    }
  }
}

Errors

invalid_domain (400) · unauthorized (401) · domain_not_available (404) · rate_limited (429) · quota_exceeded (429) · service_unavailable (503) · plan_required (403) · not_found (404) · upstream_unavailable (502)

GET/v1/rdap/{domain}

Snapshot-first normalized RDAP: entities (recursing through sub-entities), events, and nameservers. Serves a stored snapshot unless `?live=true` forces an upstream refresh. Never an upstream passthrough.

Parameters

NameInRequiredDescription
domainpathyesThe domain to look up, e.g. `example.com`. A subdomain is resolved to its registrable domain (`www.example.com` → `example.com`).
livequerynoSet to `true` to force a fresh upstream lookup (bounded by a 1-hour floor). A paid endpoint costing 2 credits — the free allowance cannot pay for it, so a free key with no purchased credits is denied with `plan_required`. A daily live refresh limit also applies. Any other value is a normal snapshot lookup.

Example response

{
  "domain": "example.com",
  "handle": "DOM-123",
  "object_class_name": "domain",
  "statuses": [
    "active"
  ],
  "rdap_conformance": [
    "rdap_level_0"
  ],
  "resource_url": "https://rdap.example/domain/example.com",
  "snapshot_time": "2026-07-30T12:00:00.000Z",
  "entities": [
    {
      "handle": "REG-1",
      "roles": [
        "registrar"
      ],
      "fn": "Example Registrar",
      "org": "Example Registrar, Inc.",
      "country_code": "US",
      "public_id_type": "IANA Registrar ID",
      "public_id": "9999",
      "child_entities": [
        {
          "handle": "ABUSE-1",
          "roles": [
            "abuse"
          ],
          "email": "abuse@example.test"
        }
      ]
    }
  ],
  "events": [
    {
      "event_action": "registration",
      "event_date": "2003-01-01T00:00:00.000Z"
    }
  ],
  "nameservers": [
    {
      "ldh_name": "ns1.example.com"
    }
  ]
}

Errors

invalid_domain (400) · unauthorized (401) · domain_not_available (404) · rate_limited (429) · quota_exceeded (429) · service_unavailable (503) · plan_required (403) · not_found (404) · upstream_unavailable (502)

GET/v1/domains/{domain}/history

A domain's history at a glance: snapshot, change, and registrar counts plus the first/last-seen window, drawn from our archive of 2B+ WHOIS/RDAP snapshots going back to 2003. Available on every plan; the full per-snapshot history is a separate endpoint.

Parameters

NameInRequiredDescription
domainpathyesThe domain to look up, e.g. `example.com`. A subdomain is resolved to its registrable domain (`www.example.com` → `example.com`).

Example response

{
  "domain": "example.com",
  "snapshot_count": 2741,
  "whois_snapshot_count": 2000,
  "rdap_snapshot_count": 741,
  "change_count": 58,
  "registrar_count": 4,
  "first_seen": "2003-01-01T00:00:00.000Z",
  "last_seen": "2026-07-01T00:00:00.000Z",
  "updated_at": "2026-07-30T00:00:00.000Z"
}

Errors

invalid_domain (400) · unauthorized (401) · domain_not_available (404) · rate_limited (429) · quota_exceeded (429) · service_unavailable (503) · not_found (404)

GET/v1/domains/{domain}/history/snapshots

The complete per-snapshot history from the 2B+ snapshot archive: every parsed WHOIS/RDAP snapshot (facts only — never raw text) plus the field-diff change timeline. A paid endpoint: a free key with no purchased credits is denied with `plan_required`. This endpoint is **async-or-cached** and does not run the archive query on the request path. A prepared result is served immediately (`200`); otherwise the call returns `202 { "status": "preparing", "retry_after": 10 }` with a `Retry-After` header — poll until it returns `200`. If preparation failed, `502 upstream_unavailable` is returned and the next call re-queues. **Charge-once:** this endpoint spends 200 credits once per domain per archive generation — the first request that enqueues (or serves) a given domain's history pays it; subsequent polls and re-reads of the same domain (within the same archive generation) spend the ordinary 1-credit lookup instead. **Errors are never charged:** a `503` (queue/metering unavailable) refunds the request, and a failed preparation (`502`) additionally refunds the 200 credits you paid — retrying after a `502` starts (and charges) fresh.

Parameters

NameInRequiredDescription
domainpathyesThe domain to look up, e.g. `example.com`. A subdomain is resolved to its registrable domain (`www.example.com` → `example.com`).

Example response

{
  "domain": "example.com",
  "generation": 1,
  "snapshot_count": 3,
  "change_count": 1,
  "registrar_count": 2,
  "first_seen": "2003-01-01T00:00:00.000Z",
  "last_seen": "2026-07-01T00:00:00.000Z",
  "snapshots": [
    {
      "snapshot_time": "2003-01-01T00:00:00.000Z",
      "protocol": "whois",
      "source": "registrar",
      "registrar": "Example Registrar, Inc.",
      "registrar_iana_id": "9999",
      "created": "2003-01-01",
      "updated": "2012-05-10",
      "expires": "2027-01-01",
      "name_servers": [
        "ns1.example.com",
        "ns2.example.com"
      ],
      "statuses": [
        "clientTransferProhibited"
      ],
      "dnssec": "unsigned",
      "privacy_protected": false,
      "contacts": {
        "registrant": {
          "organization": "Acme",
          "state_province": "CA",
          "country": "US",
          "email": "jane@acme.test"
        }
      }
    }
  ],
  "changes": [
    {
      "snapshot_time": "2003-01-01T00:00:00.000Z",
      "changed_day": "2003-01-01",
      "kind": "initial",
      "changed_fields": []
    },
    {
      "snapshot_time": "2012-05-10T00:00:00.000Z",
      "changed_day": "2012-05-10",
      "kind": "change",
      "changed_fields": [
        "registrar"
      ]
    }
  ]
}

Errors

invalid_domain (400) · unauthorized (401) · domain_not_available (404) · rate_limited (429) · quota_exceeded (429) · service_unavailable (503) · plan_required (403) · upstream_unavailable (502)

GET/v1/dns/{domain}

The zone's current records with hosting classification. Observation begins at first interest — a first query for an unobserved domain resolves it live, records the observation, and serves it; a domain with no DNS records returns `not_found`.

Parameters

NameInRequiredDescription
domainpathyesThe domain to look up, e.g. `example.com`. A subdomain is resolved to its registrable domain (`www.example.com` → `example.com`).

Example response

{
  "domain": "example.com",
  "hostnames": [
    {
      "hostname": "example.com",
      "hosting_class": "direct",
      "provider_name": null,
      "first_seen": "2025-01-01T00:00:00.000Z",
      "last_seen": "2026-07-29T00:00:00.000Z",
      "records": {
        "A": [
          "1.2.3.4"
        ],
        "NS": [
          "ns1.example.com",
          "ns2.example.com"
        ]
      }
    },
    {
      "hostname": "www.example.com",
      "hosting_class": "cdn",
      "provider_name": "Cloudflare",
      "first_seen": "2025-02-01T00:00:00.000Z",
      "last_seen": "2026-07-30T00:00:00.000Z",
      "records": {
        "CNAME": [
          "example.com"
        ]
      }
    }
  ],
  "snapshot_time": "2026-07-30T00:00:00.000Z"
}

Errors

invalid_domain (400) · unauthorized (401) · domain_not_available (404) · rate_limited (429) · quota_exceeded (429) · service_unavailable (503) · not_found (404)

GET/v1/dns/{domain}/changes

Every recorded change to this zone's DNS records, oldest first. Entries are changes only — a record set that stayed the same does not produce one — and each entry carries what changed, when it took effect, and how long it stood. An empty timeline is a valid 200 with an empty `changes` array. `snapshot_time` describes the whole timeline we hold for the zone, not just the page in hand. Requires a paid plan: on the Free plan this endpoint returns `plan_required` (403). Pass `pagination.next_cursor` back unchanged to fetch the next page; `next_cursor: null` means you have reached the last page. Entries are only ever appended, so you can keep the last cursor and re-use it later to pick up whatever has since been observed.

Parameters

NameInRequiredDescription
domainpathyesThe domain to look up, e.g. `example.com`. A subdomain is resolved to its registrable domain (`www.example.com` → `example.com`).
limitquerynoResults per page (1–1000, default 100). A value above 1000 is clamped to 1000.
cursorquerynoOpaque cursor from a prior response's `pagination.next_cursor`. Pass it back unchanged, with the same path and query parameters, to fetch the next page. A cursor used with a different query returns 400 `invalid_cursor`.

Example response

{
  "domain": "example.com",
  "changes": [
    {
      "kind": "initial",
      "hostname": "example.com",
      "first_seen": "2024-06-01T00:00:00.000Z",
      "last_seen": "2025-01-01T00:00:00.000Z",
      "hosting_class": "direct",
      "provider_name": null,
      "current": false,
      "change_summary": null,
      "records": {
        "A": [
          "1.1.1.1"
        ]
      }
    },
    {
      "kind": "change",
      "hostname": "example.com",
      "first_seen": "2025-01-01T00:00:00.000Z",
      "last_seen": "2026-07-30T00:00:00.000Z",
      "hosting_class": "direct",
      "provider_name": null,
      "current": true,
      "change_summary": {
        "added_rrtypes": [
          "A"
        ],
        "removed_rrtypes": [],
        "changed_rrtypes": [],
        "detail": [
          "A: added 2.2.2.2"
        ]
      },
      "records": {
        "A": [
          "1.1.1.1",
          "2.2.2.2"
        ]
      }
    }
  ],
  "pagination": {
    "next_cursor": null,
    "limit": 100
  },
  "snapshot_time": "2026-07-30T00:00:00.000Z"
}

Errors

invalid_domain (400) · unauthorized (401) · domain_not_available (404) · rate_limited (429) · quota_exceeded (429) · service_unavailable (503) · plan_required (403) · invalid_request (400) · invalid_cursor (400)

GET/v1/certificates/{domain}

The current leaf certificate for a host: issuer, subject, SANs, validity window, and structural chain status. Serves the stored certificate; a first query for an unobserved host probes it live (at most once per 24h), records the observation, and serves it. Certificates are host-specific, so a subdomain is looked up as-is.

Parameters

NameInRequiredDescription
domainpathyesThe host to look up, e.g. `example.com` or `shop.example.com`. Certificates are host-specific, so a subdomain is looked up as-is.

Example response

{
  "domain": "example.com",
  "subject_cn": "example.com",
  "issuer_ca": "Let's Encrypt",
  "issuer_cn": "R3",
  "issuer_org": "Let's Encrypt",
  "sans": [
    "example.com",
    "www.example.com"
  ],
  "serial": "AB12",
  "fingerprint_sha256": "AA:BB:CC",
  "valid_from": "2026-05-01T00:00:00.000Z",
  "valid_to": "2026-08-01T00:00:00.000Z",
  "key_algo": "EC",
  "key_bits": 256,
  "self_signed": false,
  "chain_status": "trusted",
  "chain_issuers": [
    "R3",
    "ISRG Root X1"
  ],
  "snapshot_time": "2026-07-30T00:00:00.000Z"
}

Errors

invalid_domain (400) · unauthorized (401) · domain_not_available (404) · rate_limited (429) · quota_exceeded (429) · service_unavailable (503) · not_found (404)

GET/v1/certificates/{domain}/changes

Every certificate this host has been observed serving, oldest first — one entry per certificate, with what changed at each rotation and the window it was served in. An empty timeline is a valid 200 with an empty `changes` array. `snapshot_time` describes the whole timeline we hold for the host, not just the page in hand. Requires a paid plan: on the Free plan this endpoint returns `plan_required` (403). Pass `pagination.next_cursor` back unchanged to fetch the next page; `next_cursor: null` means you have reached the last page. Entries are only ever appended, so you can keep the last cursor and re-use it later to pick up whatever has since been observed.

Parameters

NameInRequiredDescription
domainpathyesThe host to look up, e.g. `example.com` or `shop.example.com`. Certificates are host-specific, so a subdomain is looked up as-is.
limitquerynoResults per page (1–1000, default 100). A value above 1000 is clamped to 1000.
cursorquerynoOpaque cursor from a prior response's `pagination.next_cursor`. Pass it back unchanged, with the same path and query parameters, to fetch the next page. A cursor used with a different query returns 400 `invalid_cursor`.

Example response

{
  "domain": "example.com",
  "changes": [
    {
      "kind": "initial",
      "first_seen": "2026-02-01T00:00:00.000Z",
      "last_seen": "2026-05-01T00:00:00.000Z",
      "current": false,
      "change_summary": null,
      "cert": {
        "subject_cn": "example.com",
        "issuer_ca": "Let's Encrypt",
        "issuer_cn": "R3",
        "issuer_org": "Let's Encrypt",
        "sans": [
          "example.com",
          "www.example.com"
        ],
        "serial": "AB12",
        "fingerprint_sha256": "OLD:FP",
        "valid_from": "2026-05-01T00:00:00.000Z",
        "valid_to": "2026-08-01T00:00:00.000Z",
        "key_algo": "EC",
        "key_bits": 256,
        "self_signed": false,
        "chain_status": "trusted",
        "chain_issuers": [
          "R3",
          "ISRG Root X1"
        ]
      }
    },
    {
      "kind": "change",
      "first_seen": "2026-05-01T00:00:00.000Z",
      "last_seen": "2026-07-30T00:00:00.000Z",
      "current": true,
      "change_summary": {
        "affected": [
          "fingerprint"
        ],
        "detail": [
          "fingerprint rotated"
        ]
      },
      "cert": {
        "subject_cn": "example.com",
        "issuer_ca": "Let's Encrypt",
        "issuer_cn": "R3",
        "issuer_org": "Let's Encrypt",
        "sans": [
          "example.com",
          "www.example.com"
        ],
        "serial": "AB12",
        "fingerprint_sha256": "AA:BB:CC",
        "valid_from": "2026-05-01T00:00:00.000Z",
        "valid_to": "2026-08-01T00:00:00.000Z",
        "key_algo": "EC",
        "key_bits": 256,
        "self_signed": false,
        "chain_status": "trusted",
        "chain_issuers": [
          "R3",
          "ISRG Root X1"
        ]
      }
    }
  ],
  "pagination": {
    "next_cursor": null,
    "limit": 100
  },
  "snapshot_time": "2026-07-30T00:00:00.000Z"
}

Errors

invalid_domain (400) · unauthorized (401) · domain_not_available (404) · rate_limited (429) · quota_exceeded (429) · service_unavailable (503) · plan_required (403) · invalid_request (400) · invalid_cursor (400)

GET/v1/domains/zones/added

The domains that appeared in a TLD zone on a given day, with their nameservers. Use it to watch for newly registered domains — for example, to catch look-alikes of your brand as soon as they show up. Returns one day per call (default: the most recent available day); page through the results with the cursor, and filter by `tld` or by a substring of the domain name. Pass `pagination.next_cursor` back unchanged (with the same `date` and filters) to fetch the next page; `next_cursor: null` means you have reached the last page. Requires a paid plan.

Parameters

NameInRequiredDescription
datequerynoThe day to fetch (YYYY-MM-DD, UTC). Defaults to the most recent available day. A day with no data returns 404.
tldquerynoRestrict to these TLDs (repeatable or comma-separated), lowercased, no leading dot. Max 50. Omit for all TLDs.
limitquerynoResults per page (1–1000, default 100). A value above 1000 is clamped to 1000.
cursorquerynoOpaque cursor from a prior response's `pagination.next_cursor`. Pass it back unchanged, with the same path and query parameters, to fetch the next page. A cursor used with a different query returns 400 `invalid_cursor`.
containsquerynoOnly return domains whose name contains these substrings. Repeatable, up to 5 terms, each at least 3 characters; a domain must contain all of them.

Example response

{
  "date": "2026-08-29",
  "event": "added",
  "data": [
    {
      "domain": "example-brand-login.com",
      "tld": "com",
      "zone_date": "2026-08-29",
      "nameservers": [
        "ns1.registrar-dns.com",
        "ns2.registrar-dns.com"
      ]
    }
  ],
  "pagination": {
    "next_cursor": "v1.eyJrIjpbImNvbSIsImV4YW1wbGUuY29tIl0sImZwIjoiYTFiMiJ9",
    "limit": 100
  },
  "filters": {
    "tld": [
      "com"
    ],
    "contains": [
      "login"
    ]
  },
  "as_of": "2026-08-30T06:12:44.000Z",
  "latest_available": "2026-08-29"
}

Errors

unauthorized (401) · plan_required (403) · not_found (404) · invalid_request (400) · invalid_cursor (400) · rate_limited (429) · quota_exceeded (429) · service_unavailable (503)

GET/v1/domains/zones/changed

The domains whose nameservers changed in a TLD zone on a given day, with both the previous and the new nameservers. Use it to spot registrar or hosting migrations — for example, to detect when a domain you track moves to a different provider. Returns one day per call (default: the most recent available day); page through the results with the cursor, and filter by `tld` or by a substring of the domain name. Pass `pagination.next_cursor` back unchanged (with the same `date` and filters) to fetch the next page; `next_cursor: null` means you have reached the last page. Requires a paid plan.

Parameters

NameInRequiredDescription
datequerynoThe day to fetch (YYYY-MM-DD, UTC). Defaults to the most recent available day. A day with no data returns 404.
tldquerynoRestrict to these TLDs (repeatable or comma-separated), lowercased, no leading dot. Max 50. Omit for all TLDs.
limitquerynoResults per page (1–1000, default 100). A value above 1000 is clamped to 1000.
cursorquerynoOpaque cursor from a prior response's `pagination.next_cursor`. Pass it back unchanged, with the same path and query parameters, to fetch the next page. A cursor used with a different query returns 400 `invalid_cursor`.
containsquerynoOnly return domains whose name contains these substrings. Repeatable, up to 5 terms, each at least 3 characters; a domain must contain all of them.

Example response

{
  "date": "2026-08-29",
  "event": "ns_changed",
  "data": [
    {
      "domain": "example.com",
      "tld": "com",
      "zone_date": "2026-08-29",
      "old_nameservers": [
        "ns1.old-host.com",
        "ns2.old-host.com"
      ],
      "new_nameservers": [
        "ns1.new-host.com",
        "ns2.new-host.com"
      ]
    }
  ],
  "pagination": {
    "next_cursor": null,
    "limit": 100
  },
  "filters": {
    "tld": null,
    "contains": null
  },
  "as_of": "2026-08-30T06:12:44.000Z",
  "latest_available": "2026-08-29"
}

Errors

unauthorized (401) · plan_required (403) · not_found (404) · invalid_request (400) · invalid_cursor (400) · rate_limited (429) · quota_exceeded (429) · service_unavailable (503)

GET/v1/domains/zones/removed

The domains that were removed from a TLD zone on a given day. Use it to find dropped or expired domains — for example, to track when a name you want becomes available. Returns one day per call (default: the most recent available day); page through the results with the cursor, and filter by `tld` or by a substring of the domain name. Pass `pagination.next_cursor` back unchanged (with the same `date` and filters) to fetch the next page; `next_cursor: null` means you have reached the last page. Requires a paid plan.

Parameters

NameInRequiredDescription
datequerynoThe day to fetch (YYYY-MM-DD, UTC). Defaults to the most recent available day. A day with no data returns 404.
tldquerynoRestrict to these TLDs (repeatable or comma-separated), lowercased, no leading dot. Max 50. Omit for all TLDs.
limitquerynoResults per page (1–1000, default 100). A value above 1000 is clamped to 1000.
cursorquerynoOpaque cursor from a prior response's `pagination.next_cursor`. Pass it back unchanged, with the same path and query parameters, to fetch the next page. A cursor used with a different query returns 400 `invalid_cursor`.
containsquerynoOnly return domains whose name contains these substrings. Repeatable, up to 5 terms, each at least 3 characters; a domain must contain all of them.

Example response

{
  "date": "2026-08-29",
  "event": "removed",
  "data": [
    {
      "domain": "expired-domain.com",
      "tld": "com",
      "zone_date": "2026-08-29"
    }
  ],
  "pagination": {
    "next_cursor": null,
    "limit": 100
  },
  "filters": {
    "tld": null,
    "contains": null
  },
  "as_of": "2026-08-30T06:12:44.000Z",
  "latest_available": "2026-08-29"
}

Errors

unauthorized (401) · plan_required (403) · not_found (404) · invalid_request (400) · invalid_cursor (400) · rate_limited (429) · quota_exceeded (429) · service_unavailable (503)

GET/v1/nameservers

The nameserver hosts at or under a name, busiest first — for example, `under=example-dns.com` lists `example-dns.com` itself and every `*.example-dns.com` host a provider runs, each with the number of domains currently delegated to it. Use it to find the exact host names to pass to the per-host endpoints, or to size a provider at a glance. `under` is required and must be a domain name, not a bare TLD. Pass `pagination.next_cursor` back unchanged (with the same `under`) to fetch the next page; `next_cursor: null` means you have reached the last page.

Parameters

NameInRequiredDescription
underqueryyesThe scope to list nameservers under, e.g. `example-dns.com` returns `ns1.example-dns.com`, `ns2.example-dns.com`, …. Matches the name itself and every subdomain of it, so a full host such as `ns1.example-dns.com` lists that host. Must be a domain name such as `example-dns.com`, not a bare TLD such as `com`.
limitquerynoResults per page (1–1000, default 100). A value above 1000 is clamped to 1000.
cursorquerynoOpaque cursor from a prior response's `pagination.next_cursor`. Pass it back unchanged, with the same path and query parameters, to fetch the next page. A cursor used with a different query returns 400 `invalid_cursor`.

Example response

{
  "data": [
    {
      "host": "ns1.example-dns.com",
      "domain_count": 48213,
      "updated_at": "2026-09-09T10:41:07.000Z"
    },
    {
      "host": "ns2.example-dns.com",
      "domain_count": 48190,
      "updated_at": "2026-09-09T10:41:07.000Z"
    },
    {
      "host": "legacy.example-dns.com",
      "domain_count": 0,
      "updated_at": null
    }
  ],
  "pagination": {
    "next_cursor": null,
    "limit": 100
  },
  "filters": {
    "under": "example-dns.com"
  }
}

Errors

unauthorized (401) · invalid_request (400) · rate_limited (429) · quota_exceeded (429) · service_unavailable (503) · invalid_cursor (400)

GET/v1/nameservers/{host}

How many domains are currently delegated to a nameserver host in the zone census, and when that count last changed. A host we have never seen returns `not_found`.

Parameters

NameInRequiredDescription
hostpathyesThe nameserver host name, e.g. `ns1.example-dns.com`. Case-insensitive; a trailing dot is ignored.

Example response

{
  "host": "ns1.example-dns.com",
  "domain_count": 48213,
  "updated_at": "2026-09-09T10:41:07.000Z"
}

Errors

unauthorized (401) · invalid_request (400) · rate_limited (429) · quota_exceeded (429) · service_unavailable (503) · not_found (404)

GET/v1/nameservers/{host}/domains

The domains currently delegated to a nameserver host, in byte order of the domain name. Use it to enumerate a provider's or a competitor's customer base, or to audit every domain pointed at your own nameservers. `added_at` is the zone day we first recorded the delegation, or null for delegations that predate tracking. A known host with no delegations returns an empty `data`; a host we have never seen returns `not_found`. Pass `pagination.next_cursor` back unchanged to fetch the next page; `next_cursor: null` means you have reached the last page. The listing is not filterable by name — page through it, or start from the zone feeds for day-by-day changes.

Parameters

NameInRequiredDescription
hostpathyesThe nameserver host name, e.g. `ns1.example-dns.com`. Case-insensitive; a trailing dot is ignored.
limitquerynoResults per page (1–1000, default 100). A value above 1000 is clamped to 1000.
cursorquerynoOpaque cursor from a prior response's `pagination.next_cursor`. Pass it back unchanged, with the same path and query parameters, to fetch the next page. A cursor used with a different query returns 400 `invalid_cursor`.

Example response

{
  "host": "ns1.example-dns.com",
  "data": [
    {
      "domain": "aardvark-hosting.com",
      "added_at": null
    },
    {
      "domain": "brand-new-shop.net",
      "added_at": "2026-09-10"
    }
  ],
  "pagination": {
    "next_cursor": "v1.eyJrIjpbImJyYW5kLW5ldy1zaG9wLm5ldCJdLCJmcCI6ImExYjIifQ",
    "limit": 100
  }
}

Errors

unauthorized (401) · invalid_request (400) · rate_limited (429) · quota_exceeded (429) · service_unavailable (503) · not_found (404) · invalid_cursor (400)

Rate-limit headers

Every response carries X-RateLimit-Limit and X-RateLimit-Remaining, which describe the credit bucket that funded the call — your monthly allowance, or your purchased credits when the allowance is spent. Every metered success also carries X-Credits-Charged (the credits the call cost; 0 on a not-found, which is refunded). A 429 additionally carries Retry-After (seconds to wait) — one second for a per-second rate limit, seconds-until-midnight for the daily live refresh limit, or seconds-until-the-1st for spent credits.

Error codes

Errors are application/problem+json (RFC 7807): { "type", "title", "status", "detail" }. The type is the stable machine-readable code.

typeHTTPMeaning
invalid_domain400The domain could not be parsed.
invalid_request400A query parameter (date, tld, limit, or contains) was malformed.
invalid_cursor400The pagination cursor is malformed or does not belong to this query.
unauthorized401Missing, malformed, invalid, or revoked API key.
plan_required403A paid endpoint (live=true, a change timeline, a zone feed, full history) on a free plan with no purchased credits.
not_found404No record/observation exists for this domain yet.
domain_not_available404No record is available for this domain.
method_not_allowed405The HTTP method is not the one the endpoint serves.
payload_too_large413The request body is larger than the endpoint accepts.
rate_limited429Too many requests per second for your plan. Retry shortly.
quota_exceeded429Your credits are spent (or the daily live refresh limit is reached). See Retry-After.
upstream_unavailable502The upstream WHOIS/RDAP lookup could not be completed.
service_unavailable503The API is temporarily unable to meter the request. Retry shortly.

A note on DNS & certificate coverage

Our DNS and certificate observations grow from interest: the record for a domain begins the first time someone looks it up — a first query for an unobserved domain is answered live and recorded, so current-state lookups always work. Change timelines build from that point on, and an early query for a rarely-seen domain may return a short (or empty) one. Coverage compounds from usage, including your own.

Changelog

  • v1.0 — Launch: WHOIS, RDAP, domain history summary, and DNS & certificate current/changes endpoints.
  • v1.1 — MCP server at /v1/mcp, exposing the lookup, history, change-timeline and nameserver endpoints as tools.