ENS Indexer API

API Documentation

ENSWhois Docs Agents Status API

Public API for querying ENS domain data. All endpoints return JSON.

https://api.enswhois.com

Authentication

API Key

Pass your key via the X-API-Key header. Sign up to get a key with 1,000 free credits.

Micropayments (MPP)

Pay per request using the MPP protocol. No account needed — pay directly from your wallet.

No authentication is needed to try the API - unauthenticated requests are limited to 5 per minute. Sign up for unlimited access.

CORS is enabled for all /api endpoints. You can call these from any origin.

Lookup

GET /whois/:name

Look up a domain.

Returns domain data with ownership, expiry, resolver, wrapped status, and availability information.

Parameters

NameTypeDescription
namestringFull ENS name (e.g. vitalik.eth)

Example Response

{
  "name": "vitalik.eth",
  "display": "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

NameTypeDescription
namesstring[]Array of full ENS names (e.g. ["vitalik.eth", "nick.eth"]). Optional if namehashes is provided.
namehashesstring[]Array of namehash hex strings (0x + 64 hex chars). Optional if names is provided.

At least one of names or namehashes must be provided. Both can be used together in a single request.

{
  "names": ["vitalik.eth", "nick.eth"],
  "namehashes": ["0xee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835"]
}

Example Response

{
  "results": [
    {
      "query": "vitalik.eth",
      "name": "vitalik.eth",
      "display": "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
  }
}
GET /search-domains Paginated

Search domains with filters.

Query Parameters

NameTypeDescription
qstringSearch query (matches label by default)
search_full_namestringSet to true to search full_name instead of label
availability_statusstringFilter: registered, grace, premium, available
min_lengthintegerMinimum label length
max_lengthintegerMaximum label length
registrar_ownerstringFilter by registrar owner address (exact, case-insensitive)
wrapper_ownerstringFilter by wrapper owner address (exact, case-insensitive)
registry_ownerstringFilter by registry owner address (exact, case-insensitive)
resolverstringFilter by resolver address (exact, case-insensitive)
expiry_fromintegerMinimum expiry (unix timestamp)
expiry_tointegerMaximum expiry (unix timestamp)
is_wrappedstringtrue or false
sort_bystringfull_name (default), label, expiry_timestamp, effective_owner_address

Example Response

{
  "results": [
    {
      "name": "vitalik.eth",
      "display": "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
}

Events & Sub-Resources

GET /domain/:name/events

Get all on-chain events for a domain. Only non-null fields are included in each event object. A paid renewal through a v1 controller also has duration (seconds) and cost_usd, its exact price in USD; cost is as logged, which for the 2023 controller (CONTROLLER_16925617) is the amount sent, of which all but the price was refunded.

Parameters

NameTypeDescription
namestringFull ENS name (e.g. vitalik.eth)

Example Response

{
  "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 Paginated

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

NameTypeDescription
namestringParent domain name (e.g. vitalik.eth)

Query Parameters

NameTypeDescription
sort_bystringfull_name (default)

Example Response

{
  "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. Each entry has registry: ensv1, paid in ETH (cost_wei, cost_eth; a paid renewal also has cost_usd, its exact price in USD, payment_symbol: "ETH" and duration in seconds, and a renewal through the 2023 controller, which logs the amount sent rather than the price, has cost_eth null and sent_wei, sent_eth instead), or ensv2, paid in a stablecoin through the ENSv2 registrar (payment_token, payment_symbol, cost_amount in the token's units, cost_usd, duration in seconds, referrer; cost_wei and cost_eth are null).

Parameters

NameTypeDescription
namestringFull ENS name (e.g. vitalik.eth)
mergedstringtrue (default) merges events from the same transaction; false returns individual events with a contract field

Example Response

{
  "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

NameTypeDescription
namestringFull ENS name (e.g. vitalik.eth)

Query Parameters

NameTypeDescription
typesstringComma-separated: text, addr, contenthash (default: all)

Example Response

{
  "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"
}
GET /domain/:name/records/text/:key

Look up a single text record for a domain.

Parameters

NameTypeDescription
namestringFull ENS name (e.g. vitalik.eth)
keystringText record key (e.g. com.x, avatar)

Example Response

{
  "domain": "vitalik.eth",
  "resolver": {
    "status": "indexed",
    "chain": "mainnet",
    "address": "0x231b0ee1..."
  },
  "key": "com.x",
  "value": "VitalikButerin",
  "block_number": 18500000,
  "transaction_hash": "0xabc...",
  "source": "indexed"
}

Derived History

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. reason says which owner it is: ensv2 (the ENSv2 token holder, which outranks v1 once a name is on ENSv2), wrapped, registrar or registry.

Parameters

NameTypeDescription
namestringFull ENS name (e.g. vitalik.eth)
auditstringtrue to include per-contract breakdown (registry, registrar, wrapper, ensv2 arrays). Default false

Example Response

{
  "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, with registry ensv1 or ensv2. Once a name is registered on ENSv2 (migrated or registered again), its resolver changes come from ENSv2, and later v1 resolver changes are ignored. Block numbers are numbers.

Parameters

NameTypeDescription
namestringFull ENS name (e.g. vitalik.eth)

Example Response

{
  "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
    }
  ]
}
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

NameTypeDescription
namestringFull ENS name (e.g. vitalik.eth)
keystringText record key (e.g. avatar, com.twitter)

Example Response

{
  "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..."
    }
  ]
}

Each entry's source indicates provenance:

  • indexed - from an on-chain TextChanged event
  • resolver_change - the resolver changed, invalidating the previous value
  • live - current value from live on-chain resolution

Hash Lookups

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

NameTypeDescription
hashstringA 66-character hex string (0x + 64 hex chars)

Example Response (namehash match)

{
  "type": "namehash",
  "name": "vitalik.eth",
  "display": "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)

{
  "type": "labelhash",
  "domains": [
    {
      "name": "vitalik.eth",
      "display": "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)

{
  "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

NameTypeDescription
hashstringNamehash - a 66-character hex string (0x + 64 hex chars)

Example Response

{
  "name": "vitalik.eth",
  "display": "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 Paginated

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

NameTypeDescription
hashstringLabelhash - a 66-character hex string (0x + 64 hex chars)

Example Response

{
  "domains": [
    {
      "name": "vitalik.eth",
      "display": "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
}

ENSv2

GET /domain/:name/ensv2

A .eth second-level name's position on ENSv2, read live from mainnet (cached for 2 minutes): its reservation on the v2 .eth registry and whether that is in sync with v1, when it stops resolving and when it is released, what a renewal costs and where, and how it would migrate.

Every active or in-grace v1 name is reserved on v2 with an expiry of its v1 expiry plus 62 days, and ENS Labs' daily pre-migration sync (BatchRegistrar) keeps the two in step. ENSv2 then gives 28 days of grace, so the name is released at v1 expiry plus 90 days, the same day as on v1. A name in v2 grace doesn't resolve: since the resolution cutover (27 October 2026) names resolve through ENSv2 only.

Subnames and DNS names return applicable: false.

Parameters

NameTypeDescription
namestringA .eth 2LD, e.g. nick.eth

Response fields

FieldDescription
statereserved, reserved_pending_sync (v1 renewed, not yet synced), not_resolving (v2 reservation lapsed, v1 still in grace), not_reserved, migrated, released, unregistered
v1 / v2Expiries (unix seconds and ISO), v1 state, v2 status, in_sync, resolves, and the v2 owner once migrated
released_atWhen the name becomes registrable by anyone if not renewed
noticeA warning or note for the holder, or null
renewal1-year price on ENSv2 (one_year_usd), on v1 while it is still the way to renew (v1_one_year_usd), and via: ens_v1, eth_renewer_v1 or ens_v2_registrar
migrationPath (unwrapped, wrapped_unlocked, wrapped_locked), controller, ready, blockers, and the v2 permissions the name would get
rolloutLive flags: v1_frozen, registrations_open, migration_open

Example Response

{
  "name": "jefflau.eth",
  "applicable": true,
  "state": "reserved",
  "v1": { "expiry": 1935676872, "grace_ends": 1943452872, "state": "active" },
  "v2": { "status": "reserved", "expiry": 1941033672, "in_sync": true, "resolves": true, "owner": null },
  "released_at": 1943452872,
  "notice": null,
  "renewal": { "one_year_usd": 8.00002, "v1_one_year_usd": 5, "length": 7, "via": "ens_v1" },
  "migration": {
    "path": "wrapped_unlocked",
    "ready": false,
    "blockers": [{ "code": "migration_not_open", "message": "..." }],
    "token_roles": ["SET_SUBREGISTRY", "SET_SUBREGISTRY (admin)", "SET_RESOLVER", "SET_RESOLVER (admin)", "CAN_TRANSFER (admin)"]
  },
  "rollout": { "v1_frozen": false, "registrations_open": false, "migration_open": false }
}

Price by term for any name: enswhois.com/ensv2/pricing.

ERC-8004 Agents

GET /domain/:name/agent

Is this ENS name an ERC-8004 agent?

We consider a name an agent when it has an on-chain binding in the Adapter8004 adapter.

The Integrating ENS Agents guide covers the trust model and how to render the profile safely.

Parameters

NameTypeDescription
namestringFull ENS name (e.g. enswhois.eth)

Response Fields

FieldTypeDescription
profileobjectThe agent's ENSIP-26 records: context (agent-context) and endpoints.mcp/a2a/web. Self-asserted - treat as untrusted.
registrations[]arrayAn array (for forward-compatability) of agent registration data.

Example Response

{
  "name": "enswhois.eth",
  "display": "ENSWhois.eth",
  "namehash": "0x75aca3b6...",
  "owner": "0xac50ce32...",
  "is_wrapped": false,
  "expiry_timestamp": 1812548051,
  "is_expired": false,
  "avatar": "https://...",
  "profile": {
    "context": "Agentic interface to ENSWhois.com...",
    "endpoints": {
      "mcp": "https://mcp.enswhois.com/mcp",
      "a2a": null,
      "web": "https://enswhois.com/"
    }
  },
  "registrations": [
    {
      "registry": {
        "caip10": "eip155:1:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
        "erc7930": "0x000100000101148004a169fb4a3325136eb29fa0ceb6d2e539a432"
      },
      "agent_id": "34339",
      "has_ensip25_registration": true,
      "is_bound": true,
      "binding": {
        "adapter": "0xde152afb7db5373f34876e1499fbd893a82dd336",
        "token_standard": 0,
        "token_contract": "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85",
        "token_id": "1073289...",
        "token_uri": "https://...",
        "agent_owner": "0xac50ce32...",
        "registered_block": 25280217,
        "registered_at": "2026-06-09T13:37:59.000Z",
        "registered_by": "0xac50ce32..."
      }
    }
  ]
}
GET /agents Paginated

This endpoint returns all ENS agents. We consider a name an agent when it has an on-chain binding in the Adapter8004 adapter.

The Integrating ENS Agents guide covers the trust model and how to render the profile safely.

Query Parameters

NameTypeDescription
pageintegerPage number (25 per page)
qstringFilter by name substring
sortstringregistered (default) or name
dirstringasc or desc
hasstringComma-separated filters: mcp, a2a, web, context (record presence), and ensip25 (has a valid conformant ENSIP-25 registration)

Example Response

{
  "agents": [
    {
      "name": "enswhois.eth",
      "display": "ENSWhois.eth",
      "namehash": "0x75aca3b6...",
      "owner": "0xac50ce32...",
      "is_wrapped": false,
      "expiry_timestamp": 1812548051,
      "is_expired": false,
      "avatar": "https://...",
      "profile": {
        "context": "Agentic interface to ENSWhois.com...",
        "endpoints": { "mcp": "https://...", "a2a": null, "web": "https://..." }
      },
      "registrations": [
        {
          "registry": {
            "caip10": "eip155:1:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
            "erc7930": "0x000100000101148004a169fb4a3325136eb29fa0ceb6d2e539a432"
          },
          "agent_id": "34339",
          "has_ensip25_registration": true,
          "is_bound": true,
          "binding": {
            "adapter": "0xde152afb...",
            "token_standard": 0,
            "token_contract": "0x57f1887a...",
            "token_id": "1073289...",
            "token_uri": null,
            "agent_owner": null,
            "registered_block": 25280217,
            "registered_at": "2026-06-09T13:37:59.000Z",
            "registered_by": "0xac50ce32..."
          }
        }
      ]
    }
  ],
  "total": 49,
  "page": 1,
  "page_size": 25
}
Building an integration? The Integrating ENS Agents guide covers the trust model (what an on-chain binding does and doesn't prove), how to render profile data safely, and the three integration patterns.