Domain Intelligence

WHOIS API JSON Response: Every Field Explained with Real Examples

March 15, 202611 min readWHOIS · JSON · API Reference

Why JSON Beats Raw WHOIS Text

Raw WHOIS text is one of the least predictable data formats on the internet. Every registry formats its output differently: date fields appear as 2023-01-15, 15-Jan-2023, or 2023-01-15T00:00:00Z depending on the TLD. Contact labels switch between English, French, and Spanish. Status codes are sometimes URLs, sometimes bare keywords. A parser that works for .com breaks on .de and silently returns null on .io.

A WHOIS JSON API abstracts all of that away. The WhoisJSON API queries the authoritative source (WHOIS protocol or RDAP), normalizes every field into a consistent schema, and returns structured JSON — the same keys, the same types, the same date format, every time. This article documents every field in that response so you know exactly what to expect and how to use it.

What Does a WHOIS API JSON Response Look Like?

Below is a real, annotated response for github.com retrieved via RDAP (the modern replacement for the legacy WHOIS protocol — see the WHOIS vs RDAP guide for the full comparison). RDAP responses include additional enrichment fields marked withrdap only below.

GET /api/v1/whois?domain=github.comJSON
{
  "name":         "github.com",
  "idnName":      "github.com",
  "registered":   true,
  "source":       "rdap",
  "server":       "delta",

  "created":      "2007-10-09",
  "changed":      "2024-09-07",
  "expires":      "2026-10-09",

  "age": {
    "days":               6001,
    "months":             197,
    "years":              16,
    "isNewlyRegistered":  false,
    "isYoung":            false
  },

  "expiration": {
    "daysLeft":       208,
    "isExpiringSoon": false,
    "isExpired":      false
  },

  "status": [
    "clientDeleteProhibited",
    "clientTransferProhibited",
    "clientUpdateProhibited",
    "serverDeleteProhibited",
    "serverTransferProhibited",
    "serverUpdateProhibited"
  ],

  "statusAnalysis": {
    "clientDeleteProhibited":   true,
    "clientTransferProhibited": true,
    "clientUpdateProhibited":   true,
    "serverDeleteProhibited":   true,
    "serverTransferProhibited": true,
    "serverUpdateProhibited":   true,
    "isActive":                 true,
    "isOnHold":                 false,
    "isPendingDelete":          false,
    "isPendingTransfer":        false,
    "isRedemptionPeriod":       false
  },

  "nameserver": [
    "dns1.p08.nsone.net",
    "dns2.p08.nsone.net",
    "dns3.p08.nsone.net",
    "dns4.p08.nsone.net",
    "ns-1283.awsdns-32.org",
    "ns-1707.awsdns-21.co.uk",
    "ns-421.awsdns-52.com",
    "ns-520.awsdns-01.net"
  ],

  "nsAnalysis": {
    "count":                  8,
    "list":                   ["dns1.p08.nsone.net", "..."],
    "detectedProviders":      ["nsone", "awsdns"],
    "detectedProviderCount":  2,
    "uniqueNsDomains":        3,
    "mixedInfrastructure":    true,
    "singleInfrastructure":   false,
    "selfHosted":             false
  },

  "dnssec":       "unsigned",
  "whoisserver":  "whois.verisign-grs.com",
  "ask_whois":    "whois.verisign-grs.com",
  "ips":          "140.82.121.4",
  "parsedContacts": true,

  "registrar": {
    "id":    292,
    "name":  "MarkMonitor Inc.",
    "email": "abusecomplaints@markmonitor.com",
    "url":   "https://www.markmonitor.com",
    "phone": "+1.2086851750"
  },

  "contacts": {
    "owner": [],
    "admin": [],
    "tech":  []
  }
}
Quota header: every response also sets a Remaining-Requests header (e.g. Remaining-Requests: 847) so you can track consumption without an extra API call.

Core Fields: What Each One Means

registered

Boolean — the single most important field. true if the domain is currently registered, false if it is available. Always check this before reading any other field: an unregistered domain returns only registered: false and name; all other fields will be absent or null.

name / idnName

name is the ASCII representation of the domain (e.g. github.com). idnName is the internationalized form decoded from Punycode — identical to name for standard ASCII domains, but different for IDN domains such as xn--nxasmq6b.com (which decodes to a Greek script domain).

created, changed, expires

ISO 8601 date strings ( YYYY-MM-DD or full datetime depending on registrar precision). created is the original registration date. changed is the last modification date at the registry level (nameserver update, status change, etc.). expires is the expiry date after which the domain becomes available for re-registration. These three fields are present for both WHOIS and RDAP responses.

registrar

Object with id, name, email, url, and phone. The registrar is the company through which the domain was purchased (e.g. GoDaddy, Namecheap, MarkMonitor). Useful for abuse reports, brand protection workflows, and registrar-based filtering.

nameserver

Array of strings listing the authoritative DNS nameservers for the domain. Nameserver changes are a key signal in domain monitoring — a sudden NS change on a monitored asset can indicate a hijacking attempt or an unauthorized zone transfer.

status

Array of EPP status codes indicating what operations are currently allowed or prohibited on the domain. Common values include clientTransferProhibited (transfer to another registrar is locked), clientDeleteProhibited (domain cannot be deleted), and pendingDelete (domain is queued for deletion and about to become available).

contacts

Object with three arrays: owner, admin, and tech. Each entry contains up to 14 sub-fields ( handle, name, organization, email, address, city, country, etc.). On most gTLDs these arrays will be empty because GDPR and registrar privacy policies redact contact information. They remain populated for many ccTLDs and non-privacy-protected domains.

dnssec

String indicating the DNSSEC status. Common values: unsigned (no DNSSEC), signedDelegation (DNSSEC is active with DS records at the parent), or unsigned delegation. Relevant for security-critical domains where DNS spoofing resistance matters.

source

Either rdap or whois. This field tells you which protocol was used to retrieve the data. When source is rdap the response includes additional enrichment fields ( age, expiration, statusAnalysis, nsAnalysis) that are computed server-side from the structured RDAP data.

parsedContacts

Boolean — true when the API successfully extracted at least one contact block from the raw data. false means contact parsing either failed or was not attempted (privacy-protected domain, unsupported TLD template, etc.).

ips

String — the resolved IP address of the domain at query time. Not available for all domains. Useful when you want to correlate WHOIS data with IP reputation without a separate DNS lookup.

whoisserver / ask_whois

The WHOIS server that was queried to retrieve the data (e.g. whois.verisign-grs.com for .com domains). ask_whois carries the same value and is retained for backwards compatibility.

rawdata

Array of strings — the original, unprocessed lines returned by the WHOIS server before parsing. Useful for debugging or when you need a field that the normalized response does not expose.

exception

String — present only when the WHOIS query encountered a recoverable error (e.g. the WHOIS server was temporarily unavailable). The normalized fields may still be partially populated. If exception is set, treat the response with caution and consider retrying.

RDAP vs Legacy WHOIS: How It Affects the JSON Response

When the API retrieves data via RDAP (available for most gTLDs and a growing number of ccTLDs), the response is enriched with four additional computed objects. See the full WHOIS vs RDAP breakdown for protocol-level details.

agerdap only

Pre-computed age of the domain relative to its created date. Contains days, months, years (integer counts), plus two boolean flags:

  • isNewlyRegistered — true when the domain is 30 days old or less. This is the most reliable signal for phishing kit detection.
  • isYoung — true when the domain is 365 days old or less. A softer risk indicator used for tiered alerting.
age objectJSON
"age": {
  "days":               12,
  "months":             0,
  "years":              0,
  "isNewlyRegistered":  true,
  "isYoung":            true
}

expirationrdap only

Pre-computed expiry status relative to today. Removes the need to parse and compare date strings yourself.

  • daysLeft — integer number of days until expiry. Negative means the domain is already expired.
  • isExpiringSoon — true when daysLeft is 30 or less (and positive).
  • isExpired — true when daysLeft is negative.
expiration objectJSON
"expiration": {
  "daysLeft":       18,
  "isExpiringSoon": true,
  "isExpired":      false
}

statusAnalysisrdap only

Parsed boolean representation of each EPP status code. Instead of scanning the status array for string matches, you can read individual flags directly. Includes all six standard client/server lock flags plus:

  • isActive — the domain is registered and resolving normally.
  • isOnHold — the domain is suspended by the registry.
  • isPendingDelete — the domain is queued for deletion. May become available for registration soon.
  • isPendingTransfer — a registrar transfer is in progress.
  • isRedemptionPeriod — the domain is in the grace period after expiry, before it enters pending delete.

nsAnalysisrdap only

DNS infrastructure analysis computed from the nameserver list. Useful for risk scoring and infrastructure classification.

nsAnalysis objectJSON
"nsAnalysis": {
  "count":                 8,
  "list":                  ["dns1.p08.nsone.net", "ns-421.awsdns-52.com"],
  "detectedProviders":     ["nsone", "awsdns"],
  "detectedProviderCount": 2,
  "uniqueNsDomains":       3,
  "mixedInfrastructure":   true,
  "singleInfrastructure":  false,
  "selfHosted":            false
}
  • detectedProviders — recognized DNS providers matched by pattern (informational, may not be exhaustive).
  • uniqueNsDomains — number of distinct registrable domains across all NS entries. A value of 1 means a single DNS provider.
  • mixedInfrastructure — true when multiple distinct DNS providers are in use (resilience signal).
  • selfHosted — true when at least one nameserver is under the same domain (e.g. ns1.example.com for example.com). Indicates the domain owner manages their own DNS.

Handling Edge Cases in the JSON Response

Production integrations must handle four situations gracefully: unregistered domains, privacy-protected records, unsupported TLDs, and network timeouts. Here is defensive code in both JavaScript and Python.

Unregistered domain

When registered is false, no other fields are meaningful. Guard against this first.

edge-cases.jsJavaScript
const data = await fetchWhois("this-domain-does-not-exist-xyz123.com");

if (!data.registered) {
  console.log("Domain is available — no WHOIS data.");
  // do not attempt to read data.registrar, data.created, etc.
  return;
}

const registrar = data.registrar?.name ?? "N/A";
const expires   = data.expires ?? "N/A";
edge-cases.pyPython
data = whois_lookup("this-domain-does-not-exist-xyz123.com")

if not data.get("registered", False):
    print("Domain is available — no WHOIS data.")
    return

registrar = data.get("registrar", {}).get("name", "N/A")
expires   = data.get("expires", "N/A")

Privacy protection (GDPR / proxy)

For most gTLDs, contacts are redacted. parsedContacts will be false and the contacts arrays will be empty. Never assume contact fields are populated — always use .get() or optional chaining.

privacy.jsJavaScript
// Safe access pattern — works whether contacts are present or redacted
const ownerEmail = data.contacts?.owner?.[0]?.email ?? "redacted";
const ownerOrg   = data.contacts?.owner?.[0]?.organization ?? "redacted";

Legacy WHOIS source (no enrichment fields)

For TLDs that do not yet support RDAP, the API falls back to the legacy WHOIS protocol. source will be whois and the age, expiration, statusAnalysis, and nsAnalysis objects will be absent. Always check for their existence before accessing.

source-guard.pyPython
source     = data.get("source", "whois")
age        = data.get("age") or {}       # None when source = whois
expiration = data.get("expiration") or {}

is_new    = age.get("isNewlyRegistered", False)
days_left = expiration.get("daysLeft")   # None when not available

if source == "whois" and not days_left:
    # Parse expires manually as fallback
    from datetime import date
    expires_str = data.get("expires", "")
    # ... date parsing logic

The exception field

When the upstream WHOIS server is slow or partially down, the response may include an exception field alongside partial data. Log the exception value, decide whether the partial data is usable for your use case, and schedule a retry.

exception-handling.jsJavaScript
if (data.exception) {
  console.warn(`[WHOIS] Partial response for ${data.name}: ${data.exception}`);
  // Decide: use partial data or mark as stale and retry later
}

Real-World Examples by Use Case

Phishing Detection

Phishing domains are almost always newly registered, use privacy protection, and are registered via discount registrars. The fields to watch:

FieldHigh-risk signal
age.isNewlyRegisteredtrue — domain is less than 30 days old
age.isYoungtrue — domain is less than 1 year old
parsedContactsfalse — all contacts are redacted
statusAnalysis.clientTransferProhibitedfalse — no transfer lock (low-effort registration)
nsAnalysis.selfHostedfalse with unknown provider — hosted on a free/cheap DNS
dnssecunsigned — no DNSSEC protection

Use these fields to build a risk score. See also the Python guide for a complete newly-registered domain detection script.

Domain Monitoring

Store a baseline snapshot and compare on each poll. The fields that change most often and carry the most signal:

  • nameserver — a change here is always high-priority. Array comparison catches hijacking and unauthorized delegation changes.
  • expires / expiration.isExpiringSoon — alert when daysLeft drops below your threshold (e.g. 30 days).
  • registrar.name — an unexpected registrar change is a critical security event.
  • status — appearance of pendingDelete or disappearance of transfer locks warrants immediate review.

The WhoisJSON monitoring feature automates this for you with configurable alerts on any field change.

Brand Protection

When scanning for brand impersonation or typosquatting, the ownership fields matter most:

  • registrar.name — legitimate brand domains are typically registered through enterprise registrars (MarkMonitor, CSC) with strong locks. Consumer registrars on a brand-like domain are a red flag.
  • contacts.owner[0].organization — when not redacted, the organization name is direct evidence of ownership.
  • statusAnalysis.serverDeleteProhibited + serverTransferProhibited — server-side locks are applied by the registry at the brand owner's request. Their absence on an important brand domain is an anomaly.
  • dnssec — major brands almost always sign their zones.

How to Query the WHOIS API and Get JSON in 30 Seconds

All you need is an API key from your free account. The base URL is https://whoisjson.com/api/v1 and the Authorization header must be set to Token=YOUR_API_KEY.

cURL

terminalShell
curl -s \
  -H "Authorization: Token=YOUR_API_KEY" \
  "https://whoisjson.com/api/v1/whois?domain=github.com" \
  | jq .

JavaScript (native fetch)

whois.mjsJavaScript
const API_KEY  = "YOUR_API_KEY";
const BASE_URL = "https://whoisjson.com/api/v1";

async function whoisLookup(domain) {
  const res = await fetch(`${BASE_URL}/whois?domain=${domain}`, {
    headers: { Authorization: `Token=${API_KEY}` },
    signal:  AbortSignal.timeout(10_000),
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

const data = await whoisLookup("github.com");
console.log(data.registrar?.name);          // "MarkMonitor Inc."
console.log(data.age?.isNewlyRegistered);   // false
console.log(data.expiration?.daysLeft);     // 208

For a full Node.js integration guide with bulk lookups and error handling, see How to Query WHOIS Data in Node.js.

Python

whois.pyPython
import requests

API_KEY  = "YOUR_API_KEY"
BASE_URL = "https://whoisjson.com/api/v1"

def whois_lookup(domain: str) -> dict:
    r = requests.get(
        f"{BASE_URL}/whois",
        params={"domain": domain},
        headers={"Authorization": f"Token={API_KEY}"},
        timeout=10,
    )
    r.raise_for_status()
    return r.json()

data = whois_lookup("github.com")
print(data.get("registrar", {}).get("name"))          # MarkMonitor Inc.
print((data.get("age") or {}).get("isNewlyRegistered"))  # False
print((data.get("expiration") or {}).get("daysLeft"))    # 208

For async bulk lookups with aiohttp, see the full Python WHOIS API guide.

Conclusion

The WhoisJSON API returns a single, consistent JSON object regardless of which TLD you query or whether the data comes from RDAP or legacy WHOIS. Core fields like registered, created, expires, registrar, and nameserver are always present when a domain is registered. RDAP responses add four computed objects — age, expiration, statusAnalysis, and nsAnalysis — that eliminate most of the date arithmetic and status-string parsing you would otherwise write yourself.

Always guard against registered: false, use optional chaining or .get() for privacy-protected fields, and check source before accessing RDAP-only objects. With those three habits in place, your integration will handle every TLD and every edge case without surprises.

Start querying WHOIS data today

1,000 free requests per month. No credit card required. JSON responses in milliseconds.

Frequently Asked Questions

What fields are returned by a WHOIS API JSON response?

Every response includes name, registered, source, created, expires, changed, registrar, nameserver, status, dnssec, and contacts. When source is rdap the response also includes age, expiration, statusAnalysis, and nsAnalysis.

What is the difference between WHOIS JSON and RDAP JSON?

Legacy WHOIS returns unstructured text that must be parsed per-registrar. RDAP is the modern replacement with a defined JSON schema at the protocol level. The WhoisJSON API normalizes both into the same response format, but RDAP responses contain additional enrichment objects ( age, expiration, statusAnalysis, nsAnalysis) that are not available from legacy WHOIS. The source field tells you which protocol was used.

How do I parse a WHOIS API response in Python?

Use requests.get() with the Authorization: Token=YOUR_API_KEY header, then call .json() on the response. Always use .get() for key access ( data.get("registrar", {}).get("name")) because some fields are absent on privacy-protected or legacy-WHOIS domains. See the full Python guide for a complete implementation.

Why are some WHOIS fields null or missing?

Three common reasons: (1) GDPR / registrar privacy protection has redacted the contacts; (2) the TLD only supports legacy WHOIS, so RDAP enrichment fields like age and expiration are absent; (3) the registrar's WHOIS server did not return a standard field for that record. The parsedContacts boolean and the source field help you distinguish between these cases programmatically.

Which WHOIS API returns normalized JSON?

The WhoisJSON API ( https://whoisjson.com/api/v1) returns normalized JSON for all supported TLDs. It handles WHOIS/RDAP routing automatically, normalizes date formats, computes helper fields ( age, expiration), and returns consistent key names across all registries. Free accounts get 1,000 requests per month with no credit card required.

Put it into practice

WHOIS, DNS, SSL, subdomain discovery and domain monitoring — one API token, 1,000 free requests every month.