senapati
Documentation index

WHOIS Lookup

One endpoint for four kinds of query. Send a domain, an IP address, a CIDR range or an AS number in query and the response's type (domain, ip or asn) says which shape came back. For a domain: who sold it, when it was created and when it lapses, which name servers it points at, and the derived domain_age_days that most risk rules actually want. Answers come from the registry's RDAP server, with port-43 WHOIS as the fallback for TLDs that have none.

POST/api/whois-lookupCosts 1 creditAPI key

WHOIS lookup for a domain, IP address, CIDR range or AS number

What to expect for domains

  • source says which protocol answered: rdap for every gTLD and the country-code TLDs that run an RDAP server, whois for the rest (.de, .ru, .io and others) and whenever a registry's RDAP server is failing. The fields are the same either way.
  • lookup_status separates the three outcomes: ok, not_found (the registry says the domain is not registered) and error (no answer could be obtained, so the empty fields say nothing about the domain).
  • terms_of_service_url links the terms the registry attaches to its RDAP data. Keep it with any record you store.
  • Dates are ISO-8601 in UTC. A host name below a registered domain (www.example.co.uk) returns the registration it belongs to.
  • Since GDPR most registrant details are redacted or replaced by a proxy service. The registrant_* fields carry what the registry still exposes, and privacy_protected says when the registrant shown is a privacy service or was redacted rather than the real holder.
  • registrar_abuse_email is where to report the domain. It and the registrar's IANA ID are present for nearly every gTLD.
  • dnssec is the registry's record of the delegation: signedDelegation when it holds a DS record, otherwise unsigned. dnssec_status is what a validating resolver actually sees: secure, unsigned, zone_signed_no_ds (the zone signs its records but no DS record links it to the registry, so resolvers treat it as unsigned) or broken (a DS record exists but validation fails, so validating resolvers cannot reach the domain). It is null when the check could not be completed. dnssec_checked_at is when the state was last checked, and ds_records lists the DS records the registry holds.
  • registered_domain is the registration the record describes. It differs from domain when you query a host name such as www.example.co.uk.
  • Age is counted on UTC calendar dates; domain_age_months and domain_age_years tick over on the anniversary day.
  • status carries the EPP status codes (e.g. clientTransferProhibited). Registries with their own vocabulary are passed through as published.

Request

JSON body
FieldTypeDescription
querystring | nullWhat to look up: a domain (example.com), an IP address, a CIDR range (8.8.8.0/24) or an AS number (AS15169)
domainstring | nullDomain name to look up. Older spelling of `query`.

Authenticate with API key. Send it as the X-API-Key header.

curl -X POST "https://api.senapati.io/api/whois-lookup" \
  -H "X-API-Key: $SENAPATI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"example.org"}'

Response

Every success is wrapped in { "data": … } with a request_id; the fields below describe data. See Errors for the failure shape.

response: 200 OKWhoisData
{
"request_id": "req_8f2c1a7e",
"timestamp": "2026-10-09T09:53:42.094342+00:00",
"data": {
"type": "domain",
"query": "example.org",
"domain": "example.org",
"registered_domain": "example.org",
"registered": true,
"registrar": "ICANN",
"registrar_iana_id": "376",
"registrar_url": null,
"registrar_abuse_email": "[email protected]",
"registrar_abuse_phone": "+1.1234567890",
"creation_date": "1995-08-31T04:00:00+00:00",
"expiration_date": "2027-08-30T04:00:00+00:00",
"updated_date": "2026-08-30T04:00:10.519000+00:00",
"domain_age_days": 11362,
"domain_age_months": 373,
"domain_age_years": 31,
"name_servers": [
"katelyn.ns.cloudflare.com",
"mitch.ns.cloudflare.com"
],
"status": [
"serverDeleteProhibited",
"serverTransferProhibited",
"serverUpdateProhibited",
"autoRenewPeriod"
],
"registrant_name": null,
"registrant_organization": null,
"registrant_state": null,
"registrant_country": null,
"registrant_email_domain": null,
"registrant_email_is_free": null,
"privacy_protected": true,
"dnssec": "signedDelegation",
"dnssec_status": "secure",
"dnssec_zone_signed": true,
"dnssec_checked_at": "2026-10-09T09:53:42.088174+00:00",
"ds_records": [
{
"key_tag": 2371,
"algorithm": 13,
"digest_type": 2,
"digest": "169521AB547DF3A14C1719A0F0D5289278E22682B53CE9F22D379B922125BF98"
}
],
"emails": [
],
"terms_of_service_url": "https://thenew.org/org-people/about-pir/policies/",
"source": "rdap",
"lookup_status": "ok",
"fetched_at": "2026-10-09T09:53:42.040845+00:00",
"elapsed_ms": 743
}
}

This is the domain shape. The ip and asn shapes are shown below.

data · WhoisData
FieldTypeDescription
type"domain"default "domain"
querystring | nullThe normalised query
domain*string
registered_domainstring | nullThe registration this record describes. Differs from `domain` when a host name below it was queried
registeredboolean | nullFalse when the registry says the domain is not registered
registrarstring | null
registrar_iana_idstring | null
registrar_urlstring | null
registrar_abuse_emailstring | null
registrar_abuse_phonestring | null
creation_datestring | null
expiration_datestring | null
updated_datestring | null
domain_age_daysinteger | null
domain_age_monthsinteger | nullWhole calendar months since creation
domain_age_yearsinteger | null
name_serversstring[]
statusstring[]
registrant_namestring | null
registrant_organizationstring | null
registrant_statestring | null
registrant_countrystring | null
registrant_email_domainstring | null
registrant_email_is_freeboolean | nullThe registrant address is at a free mailbox provider
privacy_protectedboolean | nullThe registrant shown is a privacy or proxy service, or the registry redacted it
dnssecstring | nullDelegation state as the registry records it: signedDelegation when it holds a DS record, else unsigned
dnssec_status"secure" | "unsigned" | "zone_signed_no_ds" | "broken" | nullWhat a validating resolver sees. secure: DS record present and the zone validates; unsigned: DNSSEC not in use; zone_signed_no_ds: the zone signs its records but no DS record links it to the registry, so it is not protected; broken: a DS record exists but validation fails, so validating resolvers cannot reach the domain; null: could not be determined
dnssec_zone_signedboolean | nullThe zone publishes DNSKEY records
dnssec_checked_atstring | nullWhen dnssec_status was last checked (refreshed hourly)
ds_recordsobject[]DS records the registry holds: key_tag, algorithm, digest_type, digest
emailsstring[]
terms_of_service_urlstring | nullTerms the registry attaches to this data, when it links them
source"rdap" | "whois" | nullProtocol that answered the lookup
lookup_status"ok" | "not_found" | "error" | nullok: the registry answered; not_found: the domain is not registered; error: no answer could be obtained
fetched_atstring | nullWhen the registry was queried (ISO-8601)
elapsed_msintegerdefault 0

Errors

  • 401missing or invalid API key / token
  • 402not enough credits on the account
  • 422the request body or a parameter failed validation
  • 429plan rate limit exceeded

IP addresses and CIDR ranges

An address or range returns the most specific registered network that contains it, from the regional registry that holds the space (ARIN, RIPE NCC, APNIC, LACNIC or AFRINIC): network, the organization it is registered to, and the abuse_email to report it to. Private and reserved addresses are rejected with a 422 and no charge, since no registry holds a record for them.

The answer is for the address you asked about. A neighbouring address in the same network can sit in a smaller block the registry has reassigned to someone else, so look each address up rather than reusing a result for the whole range.

response: 200 OK · {"query": "8.8.8.8"}WhoisIPData
{
"request_id": "req_8f2c1a7e",
"timestamp": "2026-10-09T09:22:25.147050+00:00",
"data": {
"query": "8.8.8.8",
"handle": "NET-8-8-8-0-2",
"organization": "Google LLC",
"country": null,
"registry": "arin",
"abuse_email": "[email protected]",
"emails": [
],
"registration_date": "2023-12-28T22:24:33+00:00",
"updated_date": "2023-12-28T22:24:56+00:00",
"status": [
"active"
],
"terms_of_service_url": "https://www.arin.net/resources/registry/whois/tou/",
"source": "rdap",
"lookup_status": "ok",
"fetched_at": "2026-10-09T09:22:25.147050+00:00",
"elapsed_ms": 286,
"type": "ip",
"network": "8.8.8.0/24",
"start_address": "8.8.8.0",
"end_address": "8.8.8.255",
"ip_version": 4,
"network_name": "GOGL",
"parent_handle": "NET-8-0-0-0-0",
"network_type": "DIRECT ALLOCATION"
}
}
data · WhoisIPData
FieldTypeDescription
query*string
handlestring | nullRegistry handle
organizationstring | nullOrganisation the resource is registered to
countrystring | null
registrystring | nullRegional registry: arin, ripe, apnic, lacnic or afrinic
abuse_emailstring | nullWhere the holder accepts abuse reports
emailsstring[]
registration_datestring | null
updated_datestring | null
statusstring[]
terms_of_service_urlstring | null
source"rdap" | null
lookup_status"ok" | "not_found" | "error" | null
fetched_atstring | null
elapsed_msintegerdefault 0
type"ip"default "ip"
networkstring | nullMost specific registered network containing the query
start_addressstring | null
end_addressstring | null
ip_versioninteger | null
network_namestring | null
parent_handlestring | null
network_typestring | nullAllocation type as the registry words it

AS numbers

AS15169 and 15169 are both accepted. The record names the network operator and its abuse contact. It does not list the prefixes the AS announces; that is routing data, which registries do not hold. Private-use and unallocated numbers return lookup_status: "not_found".

response: 200 OK · {"query": "AS15169"}WhoisASNData
{
"request_id": "req_8f2c1a7e",
"timestamp": "2026-10-09T09:22:25.485915+00:00",
"data": {
"query": "AS15169",
"handle": "AS15169",
"organization": "Google LLC",
"country": null,
"registry": "arin",
"abuse_email": "[email protected]",
"emails": [
],
"registration_date": "2000-03-30T05:00:00+00:00",
"updated_date": "2012-02-24T14:44:34+00:00",
"status": [
"active"
],
"terms_of_service_url": "https://www.arin.net/resources/registry/whois/tou/",
"source": "rdap",
"lookup_status": "ok",
"fetched_at": "2026-10-09T09:22:25.485915+00:00",
"elapsed_ms": 301,
"type": "asn",
"asn": 15169,
"asn_name": "GOOGLE",
"asn_range": null
}
}
data · WhoisASNData
FieldTypeDescription
query*string
handlestring | nullRegistry handle
organizationstring | nullOrganisation the resource is registered to
countrystring | null
registrystring | nullRegional registry: arin, ripe, apnic, lacnic or afrinic
abuse_emailstring | nullWhere the holder accepts abuse reports
emailsstring[]
registration_datestring | null
updated_datestring | null
statusstring[]
terms_of_service_urlstring | null
source"rdap" | null
lookup_status"ok" | "not_found" | "error" | null
fetched_atstring | null
elapsed_msintegerdefault 0
type"asn"default "asn"
asninteger | null
asn_namestring | null
asn_rangestring | nullSet when the registry answered with a block of AS numbers

Notes

fetched_at is when the registry was asked. A lookup that fails is not kept, so a retry a few seconds later usually succeeds. When a registry's RDAP server rate-limits us, lookups for its TLDs are answered over WHOIS until it lifts the limit. IP and AS lookups have no WHOIS fallback and return error instead. The older {"domain": "example.com"} request body still works. A registry that refuses the query returns an empty shape with lookup_status: "error" rather than an HTTP error, so the call still costs one credit.