API Documentation
WHOIS/RDAP domain intelligence and cross-registrar price comparisons.
Quickstart
Get registrar prices directly from your server or terminal. No browser visit or WHOIS lookup is needed. Create a key in My Settings, set WHOIS_API_KEY in your server's environment, then run:
curl --fail-with-body --silent --show-error --max-time 30 \
-H "X-API-Key: ${WHOIS_API_KEY}" \
"https://whois.cx/api/v1/prices/example.com"
2. Read the result. registrars[] contains register, renew and transfer prices. Check currency and scraped_at; a null price is unavailable, not free. An empty list means no comparison data is available. See runnable clients for parsing and error handling.
/api/pricing/{domain} is a different, local-summary endpoint and can return null even when comparisons exist. Keep API keys server-side, never in browser code or URLs.
Choose the endpoint for your task
| I need… | GET path from https://whois.cx | Read this field |
|---|---|---|
| Registrar price comparisons | /api/v1/prices/{domain} | registrars[] |
| WHOIS and optional comparisons together | /api/v1/whois/{domain}?prices=1 | WHOIS fields and optional prices.registrars[] |
| A configured or cached domain-price summary | /api/pricing/{domain} | pricing and _meta.status; local data only on public reads |
Optional: try without a key
Anonymous access must be enabled and shares a smaller per-IP quota. A 401 means you need a valid key; successful anonymous responses include _notice.
curl --fail-with-body --silent --show-error --max-time 30 \ "https://whois.cx/api/v1/prices/example.com"
The cURL examples require cURL 7.76+ for --fail-with-body. For registration data instead, see WHOIS Lookup; that endpoint also accepts IPv4/IPv6 addresses.
Endpoints
Base URL: https://whois.cx/api/v1. The paths below are relative to this URL.
| Method | Endpoint | Description |
|---|---|---|
| GET | /whois/{domain} |
WHOIS/RDAP lookup for a domain |
| GET | /prices/{domain} |
Cross-registrar registration, renewal and transfer prices |
| GET | /account |
Your API key info and usage stats |
WHOIS Lookup
/whois/{domain}
Look up WHOIS/RDAP registration data for a domain name or an IP address. The server picks the best protocol per TLD (RDAP / WHOIS / hybrid).
Parameters
| Parameter | Type | Location | Description |
|---|---|---|---|
domain |
string | path | Required. Domain name (e.g., google.com, example.co.uk) or IPv4/IPv6 address (e.g., 8.8.8.8, 2606:4700::). IP lookups walk the RIR referral chain (ARIN → RIPE/APNIC/LACNIC/AFRINIC). |
prices |
string | query | Optional. Set to 1, true or yes (case-insensitive) to include cross-registrar prices. Domain lookups only; ignored for IP lookups. No additional quota unit. |
Response
{
"domain": "google.com",
"registrar": "MarkMonitor Inc.",
"registrar_iana_id": "292",
"registrar_abuse_email": "[email protected]",
"creation_date": "1997-09-15T04:00:00Z",
"updated_date": "2019-09-09T15:39:04Z",
"expiry_date": "2028-09-14T04:00:00Z",
"nameservers": [
"ns1.google.com",
"ns2.google.com",
"ns3.google.com",
"ns4.google.com"
],
"status": [
"client delete prohibited",
"client transfer prohibited",
"client update prohibited",
"server delete prohibited",
"server transfer prohibited",
"server update prohibited"
],
"dnssec": "unsigned",
"_meta": {
"protocol": "rdap",
"duration_ms": 245
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
domain | string | Queried domain name |
registrar | string | Domain registrar name |
registrar_iana_id | string | Registrar's IANA accreditation ID |
registrar_abuse_email | string | Registrar abuse contact email |
creation_date | string | Domain registration date (ISO 8601) |
updated_date | string | Last modification date (ISO 8601) |
expiry_date | string | Domain expiration date (ISO 8601) |
nameservers | array | List of DNS nameservers |
status | array | Domain status codes (EPP format) |
dnssec | string | DNSSEC status |
registrant_name | string | Registrant name/organization (if available) |
registrant_country | string | Registrant country (if available) |
available | boolean | Whether the domain appears unregistered |
_meta.protocol | string | Protocol used: rdap, whois, or hybrid |
_meta.duration_ms | integer | Server-side lookup duration in ms |
_cached | boolean | Present & true when the result came from Redis cache (no fresh registry hit). Omits _meta. |
_notice | string | Only present on anonymous responses. A sign-in nudge with the current anon tier limits. |
prices | object | Optional comparison payload when requested and available. Contains tld, currency, scraped_at, count, source and registrars. Omitted when comparison is disabled, no data exists, or the input is an IP. |
null.
Price Comparison
/prices/{domain}
Compare registration, renewal and transfer prices across registrars for a domain's TLD, including supported multi-part suffixes such as co.uk. Pass a domain name such as example.com, not an IP address. Use ASCII/Punycode for internationalized names. Malformed names and IP addresses return 400.
Uses the same X-API-Key authentication and optional anonymous tier as WHOIS. Each request consumes one unit from the same per-key or per-IP quota, including requests served from the server cache. It does not perform a WHOIS lookup or make a live upstream pricing request.
curl -H "X-API-Key: whois_your_key" \ https://whois.cx/api/v1/prices/example.com
scraped_at and confirm prices and promo eligibility with the registrar. They are not live domain-specific premium quotes or an availability check. Example prices below are illustrative, not current offers.
Response
{
"domain": "example.com",
"tld": "com",
"currency": "USD",
"scraped_at": "2026-10-01T00:00:00Z",
"count": 1,
"source": "tldes.com",
"registrars": [
{
"registrar": "Example Registrar",
"slug": "example-registrar",
"register": 9.99,
"register_usual": 12.99,
"register_promo": "EXAMPLE3",
"renew": 14.99,
"renew_usual": 14.99,
"renew_promo": null,
"transfer": 10.99,
"transfer_usual": 10.99,
"transfer_promo": null
}
]
}
| Field | Type | Description |
|---|---|---|
domain | string | Normalized domain. Present on this dedicated endpoint; the embedded WHOIS prices object does not repeat it. |
tld | string | Suffix used to select comparison data. |
currency | string | Currency of the returned prices; omitted when no comparison data is available. |
scraped_at | string | null | Dataset timestamp, or null if unknown. Omitted when no data is available. |
source | string | Comparison dataset source; omitted when no data is available. |
count | integer | Number of registrar rows. |
registrars | array | Sorted by effective registration price, cheapest first; rows without a registration price are last. |
registrars[].registrar / slug | string | Registrar display name and dataset identifier. |
register, renew, transfer | number | null | Effective prices within each registrar row. null means unavailable, not free. |
register_usual, renew_usual, transfer_usual | number | null | Usual prices before a listed discount. |
register_promo, renew_promo, transfer_promo | string | null | Associated promo code, if supplied. Verify conditions at checkout. |
_notice | string | Added to successful anonymous responses; describes the anonymous tier. |
No comparison data
An unsupported dataset suffix or unavailable dataset returns 200 with an empty list, not a fabricated price. Metadata such as currency and timestamp is omitted:
{
"domain": "example.com",
"tld": "com",
"count": 0,
"registrars": []
}
If price comparison is disabled, this endpoint returns 404 with {"error":"Price comparison is not available"}. Authentication and quota errors use the standard error codes.
Include prices with WHOIS
curl -H "X-API-Key: whois_your_key" \ "https://whois.cx/api/v1/whois/example.com?prices=1"
The lookup response gains a prices object containing the comparison fields above, without the repeated domain. This costs no extra quota unit. If comparison is disabled or no dataset rows exist, prices is omitted and the normal lookup continues. IP lookups never include it.
Understand Your Response
Check the HTTP status first, then the payload. A successful request does not guarantee that a price exists.
| What you received | What to do |
|---|---|
200 with nonempty registrars | Use the returned currency and timestamp. Treat each null price as unavailable and confirm offers with the registrar. |
200 with registrars: [] | Display “No comparison data available.” No browser visit is needed; do not treat missing prices as zero or retry continuously. |
WHOIS response without prices | The input was an IP, comparison is disabled, no dataset rows exist, or prices were not requested. WHOIS data is still usable. |
Summary with pricing: null | Read _meta.status. A local_miss means no usable local summary, not that browsers must populate it. Use /api/v1/prices/{domain} if you need comparisons. |
400 / 401 / 403 | Correct the input / supply a valid key / ask the operator whether the public API is enabled. Repeating the same request will not resolve these errors. |
404 | Check the full route and JSON error. “Price comparison is not available” means the feature is disabled, not that this domain has no price. |
429 | Back off. Respect Retry-After when present and the exhausted windows in limits. Do not retry in a tight loop. |
5xx, timeout or invalid JSON | Treat the request as a failure, not an empty price list. Retry only with bounded backoff. |
Website Pricing APIs
For integrations, use the versioned Price Comparison API above. The website also uses these separate public read routes. Paths here are complete paths from https://whois.cx, not relative to /api/v1; their normal reads do not require login or an API key.
| GET path | Source | Response |
|---|---|---|
/api/pricing/{domain} | Valid cache, then domain override, then TLD base prices. No live provider request for normal public reads. | domain, pricing (object or null), _meta. Price fields: register_price, renew_price, transfer_price, restore_price, plus currency, source, premium and availability metadata. |
/api/price-compare/{domain} | The comparison dataset used by /api/v1/prices/{domain}. | Comparison fields plus enabled and, when enabled, available. Disabled: enabled:false, empty registrars. Unreadable dataset: available:false, empty registrars. Both return 200. |
/api/registrar-pricing/tld/{tld} | Separate registrar-pricing database, limited to enabled registrars and pricing rows. | tld, pricing (array), cheapest (object or null), registrar_count. Database prices may differ from the comparison dataset. |
_meta.display control the price chips in the website, not which price fields the summary JSON returns. The registrar-database route is not gated by either display setting. Pricing administration, source configuration and forced-refresh diagnostics require an admin session. Domain prices are separate from the site's subscription plans.
Understanding a null pricing summary
/api/pricing/{domain} behaves the same for browsers, curl and server-side callers. The website sends the same GET request; it does not fetch provider prices separately or write them back. _meta.fetch_mode: "client_async" describes frontend request timing, not a requirement to visit the domain page or a promise that an external provider will be queried.
Read _meta.status to distinguish these successful 200 responses. A local miss is not proof that no provider has a price for the domain.
| Summary status | Meaning |
|---|---|
available | pricing contains a price object. |
local_miss | pricing is null: no usable eligible cache entry, domain override or TLD base price was found. No external pricing provider was queried. |
disabled | pricing is null because the domain pricing summary is disabled. |
refresh_miss | pricing is null after an administrator's forced refresh found no usable result. Providers may be disabled, unconfigured, unavailable or have no matching price. |
_meta.lookup_mode is local_only for ordinary reads and provider_fallback for an administrator's forced refresh. It identifies the permitted resolution policy, not evidence that a provider was actually called: local data may satisfy a refresh, or providers may be disabled or unconfigured.
{
"domain": "example.com",
"pricing": null,
"_meta": {
"fetch_mode": "client_async",
"display": {
"show_register": true,
"show_renew": true,
"show_transfer": true,
"show_restore": false
},
"status": "local_miss",
"lookup_mode": "local_only",
"message": "No usable cached price, domain override, or TLD base price is available. Public requests do not fetch external pricing providers."
}
}
To supply missing summaries, an administrator can configure local domain overrides or TLD base prices, or explicitly force a refresh against configured, enabled providers. Opening the domain page does not populate missing provider data. An API key does not grant administrator access. For cross-registrar TLD comparison data, use /api/v1/prices/{domain}; those comparisons are not domain-specific quotes.
Account Info
/account
Returns your current tier's limits and live usage. The response shape depends on whether you passed a key.
With API key
{
"tier": "api_key",
"key_name": "My App",
"key_prefix": "whois_a1b2c3",
"total_requests": 1542,
"rate_limits": { "1m": 10, "1h": 200, "1d": 2000 },
"rate_usage": {
"1m": {"limit": 10, "remaining": 7, "reset": 1709740800, "requests": 3},
"1h": {"limit": 200, "remaining": 155, "reset": 1709740800, "requests": 45},
"1d": {"limit": 2000, "remaining": 1688, "reset": 1709769600, "requests": 312}
},
"created_at": "2025-01-15 10:30:00"
}
Anonymous (no key)
{
"tier": "anonymous",
"rate_limits": { "1m": 3, "1h": 30, "1d": 100 },
"rate_usage": {
"1m": {"limit": 3, "remaining": 2, "reset": 1709740800, "requests": 1},
"1h": {"limit": 30, "remaining": 29, "reset": 1709740800, "requests": 1},
"1d": {"limit": 100, "remaining": 99, "reset": 1709769600, "requests": 1}
},
"_notice": "You're using the anonymous tier (3/min, 30/hour, 100/day). Sign in at https://whois.cx to create an API key with higher limits."
}
tier == "anonymous" or the presence of _notice to know which tier served your request.
Rate Limits
The versioned WHOIS and price-comparison endpoints share a quota: anonymous (when enabled, no key, per-IP) or API key (per-key). A dedicated prices request uses one unit; adding ?prices=1 to WHOIS uses no additional unit. Server-cache hits still count. /account reads usage without consuming a lookup unit.
Anonymous tier — no key required
| Window | Default Limit | Description |
|---|---|---|
| Per Minute | 3 | Per client IP |
| Per Hour | 30 | Per client IP |
| Per Day | 100 | Per client IP |
API key tier
| Window | Default Limit | Description |
|---|---|---|
| Per Minute | 10 | Short-burst protection |
| Per Hour | 200 | Medium-term limit |
| Per Day | 2,000 | Daily quota |
WHOIS and price requests that reach the quota check include these minute-window headers. Authentication failures and account responses do not necessarily include them:
X-RateLimit-Limit: 3 X-RateLimit-Remaining: 2 X-RateLimit-Reset: 1709740800 Retry-After: 42 ← only on 429
When a per-IP or per-key quota window is exhausted, you'll get a 429 Too Many Requests with the full window snapshot and (for anonymous callers) the _notice:
{
"error": "Rate limit exceeded",
"limits": {
"1m": {"allowed": false, "limit": 3, "remaining": 0, "reset": 1709740860, "requests": 3},
"1h": {"allowed": true, "limit": 30, "remaining": 26, "reset": 1709743200, "requests": 4},
"1d": {"allowed": true, "limit": 100, "remaining": 96, "reset": 1709769600, "requests": 4}
},
"_notice": "You're using the anonymous tier (3/min, 30/hour, 100/day). Sign in at https://whois.cx to create an API key with higher limits."
}
429 when a TLD's shared lookup limit is reached. That response can contain only error, without limits, _notice or Retry-After. Check optional fields before using them and back off on either kind of throttling.
Cache-Control: public, max-age=600. Reusing a cached response without contacting the API avoids another request. Requests that reach the API still consume quota, even when its own cache serves the data.
Authentication
Authentication is optional only when anonymous access is enabled. Otherwise, a missing key returns 401. An invalid or expired supplied key also returns 401; it does not fall back to the anonymous tier. The public API must be enabled for either tier.
Generate a key from My Settings, then pass it via the X-API-Key header:
curl -H "X-API-Key: whois_your_api_key_here" \ https://whois.cx/api/v1/whois/google.com
Error Codes
| Status | Meaning | When |
|---|---|---|
200 | OK | Successful lookup, account response, or price comparison (including an empty registrar list) |
400 | Bad Request | Invalid domain or IP format |
401 | Unauthorized | Key was sent but is invalid/expired — or no key sent while anonymous access is turned off |
403 | Forbidden | The public API is disabled entirely (api.enabled master toggle is off) |
404 | Not Found | Unknown API route, or price comparison is disabled on /prices/{domain}. Missing comparison data alone returns 200 with an empty list. |
429 | Too Many Requests | Per-IP/per-key quota exhausted, or a shared TLD lookup limit was reached (see Rate Limits) |
500 | Server Error | Internal lookup failure |
All error responses are JSON with at minimum an error string:
{"error": "Description of what went wrong"}
Code Examples
These clients fetch example.com price comparisons, check failures and print every available price. Set WHOIS_API_KEY in the environment for the recommended authenticated tier; without it, anonymous access must be enabled. All clients optionally accept WHOIS_API_BASE, defaulting to https://whois.cx/api/v1. Keep that base URL trusted: a configured key is sent to it.
Save the selected example as a script and run it from your terminal. A successful empty result prints No comparison data available.; transport, HTTP and malformed-response failures exit unsuccessfully instead.
#!/usr/bin/env bash
base="${WHOIS_API_BASE:-https://whois.cx/api/v1}"
headers=(-H "Accept: application/json")
if [ -n "${WHOIS_API_KEY:-}" ]; then
headers+=(-H "X-API-Key: ${WHOIS_API_KEY}")
fi
response_headers=$(mktemp) || exit 1
trap 'rm -f "$response_headers"' EXIT
if ! response=$(curl --fail-with-body --silent --show-error --dump-header "$response_headers" \
--connect-timeout 10 --max-time 30 "${headers[@]}" \
"${base%/}/prices/example.com"); then
# Show Retry-After and quota headers when the request fails.
cat "$response_headers" >&2
printf '%s\n' "$response" >&2
exit 1
fi
printf '%s\n' "$response" | jq -er '
def price: if . == null then "unavailable" else tostring end;
if (.registrars | type) != "array" then
error("Invalid price response")
elif (.registrars | length) == 0 then
"No comparison data available."
else
"Currency: \(.currency // "unknown") | Dataset updated: \(.scraped_at // "unknown")",
(.registrars[] | "\(.registrar): register=\(.register | price), renew=\(.renew | price), transfer=\(.transfer | price)")
end
'