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.
{
"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": []
}
} 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": {
"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 whendaysLeftis 30 or less (and positive).isExpired— true whendaysLeftis negative.
"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": {
"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.comforexample.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.
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";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.
// 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 = 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.
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:
| Field | High-risk signal |
|---|---|
age.isNewlyRegistered | true — domain is less than 30 days old |
age.isYoung | true — domain is less than 1 year old |
parsedContacts | false — all contacts are redacted |
statusAnalysis.clientTransferProhibited | false — no transfer lock (low-effort registration) |
nsAnalysis.selfHosted | false with unknown provider — hosted on a free/cheap DNS |
dnssec | unsigned — 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 whendaysLeftdrops below your threshold (e.g. 30 days).registrar.name— an unexpected registrar change is a critical security event.status— appearance ofpendingDeleteor 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
curl -s \
-H "Authorization: Token=YOUR_API_KEY" \
"https://whoisjson.com/api/v1/whois?domain=github.com" \
| jq .
JavaScript (native fetch)
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
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.