# ENSWhois API - Full Reference > Public API for querying ENS (Ethereum Name Service) domain data from the distributed ENSIndexer network. All endpoints return JSON. ## Base URL ``` https://api.enswhois.com ``` ## Authentication - **API Key**: Pass via `X-API-Key` header. Sign up at https://enswhois.com/signup for 1,000 free credits. - **Micropayments (MPP)**: Pay per request using the MPP protocol (https://www.mpp.dev/). No account needed. - **Unauthenticated**: Limited to 5 requests per minute. CORS is enabled for all endpoints. ## How It Works ### Data Sources ## Domains ### Lookup #### `GET /whois/:name` Look up a domain. Returns domain data with ownership, expiry, resolver, wrapped status, and availability information. **Parameters** | Name | Type | Description | | --- | --- | --- | | name | string | Full ENS name (e.g. `vitalik.eth`) | **Example Response** ```json { "name": "vitalik.eth", "label": "vitalik", "labelhash": "0xaf2caa1c...", "token_id": "7960091...", "namehash": "0xee6c4522...", "parent_node": "0x93cdeb7...", "parent_name": "eth", "expiry_timestamp": "1754000000", "ownership": { "is_wrapped": false, "owner": "0xd8dA6BF2...", "registry": "0xd8dA6BF2...", "registrar": "0xd8dA6BF2..." }, "resolver": { "status": "indexed", "chain": "mainnet", "address": "0x231b0ee1..." }, "availability_status": "registered", "query": "vitalik.eth", "source": "indexed" } ``` #### `POST /whois/bulk` - Bulk lookup for multiple domains by name or namehash - Maximum 500 items per request (names + namehashes combined) - Each result uses the same fields as the single whois endpoint **Request Body** | Name | Type | Description | | --- | --- | --- | | names | string[] | Array of full ENS names (e.g. `["vitalik.eth", "nick.eth"]`). Optional if `namehashes` is provided. | | namehashes | string[] | Array of namehash hex strings (`0x` + 64 hex chars). Optional if `names` is provided. | ```json { "names": ["vitalik.eth", "nick.eth"], "namehashes": ["0xee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835"] } ``` **Example Response** ```json { "results": [ { "query": "vitalik.eth", "name": "vitalik.eth", "label": "vitalik", "expiry_timestamp": "1754000000", "ownership": { "..." }, "resolver": { "..." }, "availability_status": "registered", "source": "indexed" }, { "query": "nonexistent.eth", "availability_status": "available", "source": "indexed" } ], "summary": { "total": 2, "registered": 1, "grace": 0, "premium": 0, "available": 1, "unknown": 0, "expiring_30d": 0 } } ``` ### Events & Sub-Resources #### `GET /search-domains` Search domains with filters. **Query Parameters** | Name | Type | Description | | --- | --- | --- | | q | string | Search query (matches label by default) | | search_full_name | string | Set to `true` to search `full_name` instead of `label` | | availability_status | string | Filter: `registered`, `grace`, `premium`, `available` | | min_length | integer | Minimum label length | | max_length | integer | Maximum label length | | registrar_owner | string | Filter by registrar owner address (exact, case-insensitive) | | wrapper_owner | string | Filter by wrapper owner address (exact, case-insensitive) | | registry_owner | string | Filter by registry owner address (exact, case-insensitive) | | resolver | string | Filter by resolver address (exact, case-insensitive) | | expiry_from | integer | Minimum expiry (unix timestamp) | | expiry_to | integer | Maximum expiry (unix timestamp) | | is_wrapped | string | `true` or `false` | | sort_by | string | `full_name` (default), `label`, `expiry_timestamp`, `effective_owner_address` | **Example Response** ```json { "results": [ { "name": "vitalik.eth", "label": "vitalik", "expiry_timestamp": "1754000000", "ownership": { "owner": "0xd8dA6BF2...", "is_wrapped": false } } ], "stats": { "expired": 12, "expiring_30d": 3, "expiring_90d": 8, "wrapped": 210, "wrapped_pct": 39.5 }, "total": 532, "page": 1, "page_size": 25 } ``` #### `GET /domain/:name/events` Get all on-chain events for a domain. Only non-null fields are included in each event object. **Parameters** | Name | Type | Description | | --- | --- | --- | | name | string | Full ENS name (e.g. `vitalik.eth`) | **Example Response** ```json { "events": [ { "contract": "CONTROLLER_9380470", "event_type": "NameRegistered", "block_number": 9380471, "transaction_hash": "0xabc...", "owner_address": "0xd8dA6BF2...", "cost_wei": "3200000000000000", "expiry_timestamp": "1754000000" }, { "contract": "REGISTRY", "event_type": "NewOwner", "block_number": 9380471, "transaction_hash": "0xabc...", "owner_address": "0xd8dA6BF2..." } ] } ``` #### `GET /domain/:name/subdomains` List direct subdomains of a domain. Only returns subnames created via on-chain events. Off-chain subnames (e.g. ENSIP-10 wildcard or CCIP-Read) are not discoverable by the indexer. **Parameters** | Name | Type | Description | | --- | --- | --- | | name | string | Parent domain name (e.g. `vitalik.eth`) | **Query Parameters** | Name | Type | Description | | --- | --- | --- | | sort_by | string | `full_name` (default) | **Example Response** ```json { "domain": "vitalik.eth", "results": [ { "name": "sub.vitalik.eth", "label": "sub", "expiry_timestamp": null, "ownership": { "owner": "0xd8dA6BF2...", "is_wrapped": false }, "resolver": { "address": "0x231b0Ee1..." } } ], "total": 3, "page": 1, "page_size": 25 } ``` #### `GET /domain/:name/registration-history` Registration and renewal history for a domain. **Parameters** | Name | Type | Description | | --- | --- | --- | | name | string | Full ENS name (e.g. `vitalik.eth`) | | merged | string | `true` (default) merges events from the same transaction; `false` returns individual events with a `contract` field | **Example Response** ```json { "domain": "vitalik.eth", "registrations": [ { "event_type": "NameRegistered", "block_number": 9380471, "transaction_hash": "0xabc...", "cost_wei": "3200000000000000", "cost_eth": "0.003200", "expiry_timestamp": "1754000000", "owner_address": "0xd8dA..." } ], "renewals": [] } ``` #### `GET /domain/:name/records` Returns current resolver records for a domain: text records, multi-chain addresses, and contenthash. **Parameters** | Name | Type | Description | | --- | --- | --- | | name | string | Full ENS name (e.g. `vitalik.eth`) | **Query Parameters** | Name | Type | Description | | --- | --- | --- | | types | string | Comma-separated: `text`, `addr`, `contenthash` (default: all) | **Example Response** ```json { "domain": "vitalik.eth", "resolver": { "status": "indexed", "chain": "mainnet", "address": "0x231b0ee1..." }, "records": { "texts": { "com.twitter": "VitalikButerin", "avatar": "eip155:1/..." }, "addresses": { "60": "0xd8dA6BF2..." }, "contenthash": null }, "source": "indexed" } ``` ### Derived History #### `GET /domain/:name/records/text/:key` Look up a single text record for a domain. **Parameters** | Name | Type | Description | | --- | --- | --- | | name | string | Full ENS name (e.g. `vitalik.eth`) | | key | string | Text record key (e.g. `com.x`, `avatar`) | **Example Response** ```json { "domain": "vitalik.eth", "resolver": { "status": "indexed", "chain": "mainnet", "address": "0x231b0ee1..." }, "key": "com.x", "value": "VitalikButerin", "block_number": 18500000, "transaction_hash": "0xabc...", "source": "indexed" } ``` #### `GET /domain/:name/ownership-history` Effective ownership history for a domain, derived from on-chain events. Returns ownership periods with the resolved effective owner at each point in time. **Parameters** | Name | Type | Description | | --- | --- | --- | | name | string | Full ENS name (e.g. `vitalik.eth`) | | audit | string | `true` to include per-contract breakdown (`registry`, `registrar`, `wrapper` arrays). Default `false` | **Example Response** ```json { "history": [ { "address": "0xd8dA6BF2...", "reason": "registrar", "from_block": 9500000, "from_tx": "0xdef456...", "to_block": null, "to_tx": null } ] } ``` #### `GET /domain/:name/resolver-history` Resolver change history derived from stored on-chain events. Each entry represents a period where a specific resolver contract was active for the domain. **Parameters** | Name | Type | Description | | --- | --- | --- | | name | string | Full ENS name (e.g. `vitalik.eth`) | **Example Response** ```json { "history": [ { "address": "0x4976fb03...", "from_block": 9412610, "from_tx": "0xdef456...", "to_block": 16773775, "to_tx": "0x789abc..." }, { "address": "0x231b0Ee1...", "from_block": 16773775, "from_tx": "0x789abc...", "to_block": null, "to_tx": null } ] } ``` ### Hash Lookups #### `GET /domain/:name/records/text/:key/history` Change history for a specific text record. Only includes changes from the domain's active resolver at the time - records set on non-active resolvers are excluded. Resolver changes are included as null-value boundaries. **Parameters** | Name | Type | Description | | --- | --- | --- | | name | string | Full ENS name (e.g. `vitalik.eth`) | | key | string | Text record key (e.g. `avatar`, `com.twitter`) | **Example Response** ```json { "domain": "vitalik.eth", "key": "avatar", "history": [ { "value": "eip155:1/...", "block_number": 18500000, "resolver": "0x231b0ee1...", "source": "indexed", "transaction_hash": "0xabc..." }, { "value": null, "block_number": 16773775, "source": "resolver_change" }, { "value": "old-avatar", "block_number": 15000000, "resolver": "0x4976fb03...", "source": "indexed", "transaction_hash": "0xdef..." } ] } ``` #### `GET /lookup-hash/:hash` Look up a domain by any hash. Tries namehash first (unique match), then falls back to labelhash (may match multiple domains). **Parameters** | Name | Type | Description | | --- | --- | --- | | hash | string | A 66-character hex string (`0x` + 64 hex chars) | **Example Response (namehash match)** ```json { "type": "namehash", "name": "vitalik.eth", "label": "vitalik", "token_id": "7960091...", "namehash": "0xee6c4522...", "labelhash": "0xaf2caa1c...", "parent_name": "eth", "parent_node": "0x93cdeb7...", "expiry_timestamp": "1754000000", "ownership": { "owner": "0xd8dA6BF2...", "is_wrapped": false }, "resolver": { "status": "indexed", "chain": "mainnet", "address": "0x231b0ee1..." } } ``` **Example Response (labelhash match)** ```json { "type": "labelhash", "domains": [ { "name": "vitalik.eth", "label": "vitalik", "token_id": "7960091...", "namehash": "0xee6c4522...", "labelhash": "0xaf2caa1c...", "parent_name": "eth", "parent_node": "0x93cdeb7...", "expiry_timestamp": "1754000000", "ownership": { "owner": "0xd8dA6BF2...", "is_wrapped": false }, "resolver": { "status": "indexed", "chain": "mainnet", "address": "0x231b0ee1..." } } ] } ``` **Example Response (not indexed)** ```json { "hash": "0xee6c4522...", "indexed": false } ``` #### `GET /lookup-namehash/:hash` Look up a single domain by its namehash. Returns full domain details including ownership, resolver, and wrapped status. **Parameters** | Name | Type | Description | | --- | --- | --- | | hash | string | Namehash - a 66-character hex string (`0x` + 64 hex chars) | **Example Response** ```json { "name": "vitalik.eth", "label": "vitalik", "token_id": "7960091...", "namehash": "0xee6c4522...", "labelhash": "0xaf2caa1c...", "parent_name": "eth", "parent_node": "0x93cdeb7...", "expiry_timestamp": "1754000000", "ownership": { "owner": "0xd8dA6BF2...", "registry": "0xd8dA6BF2...", "registrar": "0xd8dA6BF2...", "is_wrapped": false }, "resolver": { "status": "indexed", "chain": "mainnet", "address": "0x231b0ee1..." } } ``` #### `GET /lookup-labelhash/:hash` Look up domains by labelhash. A labelhash can match multiple domains across different parent names (e.g. `vitalik.eth` and `vitalik.xyz` share the same labelhash). **Parameters** | Name | Type | Description | | --- | --- | --- | | hash | string | Labelhash - a 66-character hex string (`0x` + 64 hex chars) | **Example Response** ```json { "domains": [ { "name": "vitalik.eth", "label": "vitalik", "token_id": "7960091...", "namehash": "0xee6c4522...", "labelhash": "0xaf2caa1c...", "parent_name": "eth", "parent_node": "0x93cdeb7...", "expiry_timestamp": "1754000000", "ownership": { "owner": "0xd8dA6BF2...", "is_wrapped": false }, "resolver": { "status": "indexed", "chain": "mainnet", "address": "0x231b0ee1..." } } ], "total": 1, "page": 1, "page_size": 25 } ``` ## Addresses #### `GET /address/:address/domains` List all domains associated with an address. **Parameters** | Name | Type | Description | | --- | --- | --- | | address | string | Ethereum address (42-char hex) | **Query Parameters** | Name | Type | Description | | --- | --- | --- | | sort_by | string | `full_name` (default) or `expiry_timestamp` | | include_expired | boolean | Include expired domains (default `false`) | | address_type | string | `effective_owner` (default), `registry_owner`, `registrar_owner`, `wrapper_owner`, `resolver` | **Example Response** ```json { "address": "0xd8da6bf2...", "address_type": "effective_owner", "results": [ { "name": "vitalik.eth", "label": "vitalik", "expiry_timestamp": "1754000000", "ownership": { "owner": "0xd8dA6BF2...", "registry": "0xd8dA6BF2...", "is_wrapped": false }, "resolver": { "address": "0x231b0Ee1..." } } ], "total": 42, "page": 1, "page_size": 25 } ``` #### `GET /address/:address/domain-count` Lightweight count-only endpoint. Returns domain counts (total, active, expired) for an address by type. **Parameters** | Name | Type | Description | | --- | --- | --- | | address | string | Ethereum address (42-char hex) | **Query Parameters** | Name | Type | Description | | --- | --- | --- | | address_type | string | `effective_owner` (default), `registry_owner`, `registrar_owner`, `wrapper_owner`, `resolver` | **Example Response** ```json { "address": "0xd8da6bf2...", "address_type": "effective_owner", "count": 42, "active_count": 40, "expired_count": 2 } ``` #### `GET /address/:address/primary-name` Reverse resolve an address to its primary ENS name. Optionally returns full history of reverse name changes. **Parameters** | Name | Type | Description | | --- | --- | --- | | address | string | Ethereum address (42-char hex) | **Query Parameters** | Name | Type | Description | | --- | --- | --- | | history | boolean | Include full reverse name history (default `false`) | **Example Response (default)** ```json { "address": "0xd8da6bf2...", "primary_name": "vitalik.eth", "registrar": "addr.reverse" } ``` **Example Response (history=true)** ```json { "address": "0xd8da6bf2...", "current": { "primary_name": "vitalik.eth", "registrar": "addr.reverse" }, "history": [ { "name": "vitalik.eth", "registrar": "addr.reverse", "block_number": 9412610, "transaction_hash": "0xabc..." } ] } ``` #### `POST /address/primary-names/bulk` Bulk reverse resolution for multiple addresses. Maximum 200 per request. **Request Body** ```json { "addresses": ["0xd8da6bf2...", "0x225f137..."] } ``` **Example Response** ```json { "results": { "0xd8da6bf2...": { "primary_name": "vitalik.eth", "registrar": "addr.reverse" }, "0x225f137...": { "primary_name": null, "registrar": null } } } ``` #### `GET /address/:address/summary` Combined address summary: domain count (by effective owner), primary name, and domains expiring soon. **Parameters** | Name | Type | Description | | --- | --- | --- | | address | string | Ethereum address (42-char hex) | **Example Response** ```json { "address": "0xd8da6bf2...", "domain_count": 42, "primary_name": "vitalik.eth", "primary_name_source": "addr.reverse", "domains_expiring_30d": 1 } ``` ## System #### `GET /stats` Get indexer statistics including block progress, domain counts, event counts, and sync status. **Example Response** ```json { "name": "indexer-1", "blocks": { "chainHead": 21500000, "fetched": 21499950, "processed": 21499900 }, "services": { "fetcher": { "running": true, "synced": true }, "processor": { "running": true, "synced": true }, "addressProcessor": { "running": true } }, "queues": { "domainsPending": 0, "domainsReady": 0, "eventsPending": 12, "eventsPendingNamehashes": 5, "eventsDirect": 3, "cachedMappings": 1500 }, "addressProcessor": { "addressCount": 245000, "eventsRemaining": 0, "lastEventId": 8500000 } } ``` ### ENS Statistics #### `GET /pending-stats` Get count of pending (unhashed) domains awaiting label resolution. **Example Response** ```json { "pendingCount": 1234 } ``` #### `GET /ens-stats` Global ENS overview. Public endpoint, no API key required. Cached for 5 minutes. **Example Response** ```json { "total_domains": 2145000, "active_domains": 1890000, "expired_domains": 255000, "wrapped_domains": 620000, "wrapped_percentage": 32.8, "expiring_30d": 18500, "expiring_90d": 52000, "unique_owners": 485000, "total_events": 8200000, "registrations_24h": 340, "registrations_7d": 2800, "renewals_7d": 4100, "generated_at": "2026-04-09T12:00:00Z" } ``` #### `GET /ens-stats/registrations` 30-day daily registration and renewal counts. Public, cached for 15 minutes. **Example Response** ```json { "daily": [ { "date": "2026-04-08", "registrations": 340, "renewals": 520 } ], "generated_at": "2026-04-09T12:00:00Z" } ``` #### `GET /ens-stats/distribution` Domain label length and TLD distribution. Public, cached for 1 hour. **Example Response** ```json { "by_length": [ { "length": 3, "count": 45000, "percentage": 2.4 } ], "by_tld": [ { "tld": "eth", "count": 1800000 } ], "generated_at": "2026-04-09T12:00:00Z" } ```