ENS Indexer API

API Documentation

Home

Public API for querying ENS domain data from the distributed ENSIndexer network. 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.

Data Sources

The API serves data from two sources, transparently chosen per request:

  • Indexed - event-sourced from on-chain ENS contracts, stored in the indexer database. Complete and authoritative for contracts we track.
  • Live - resolved on-chain in real-time via the Universal Resolver. Used when the indexer doesn't cover a domain's resolver, or the domain isn't in the indexer at all (e.g. L2 names on a mainnet-only indexer).

The API always returns authoritative data. It never returns data it knows to be stale. If live resolution is needed but fails, a 502 error is returned.

How Live Resolution Works

The API proxy sits between consumers and the indexer network. For each request:

  1. The proxy forwards the request to a synced indexer
  2. The indexer returns the domain data with a resolver object indicating whether the resolver is indexed, unindexed, or none
  3. If the resolver status is not indexed, the proxy resolves the data live on-chain
  4. Live data replaces the indexer's response and is returned with source: "live"

Live resolution uses two parallel RPC calls:

  • Universal Resolver - batches addr, contenthash, and text record lookups into a single resolve() call. Supports ENSIP-10 wildcard resolution and CCIP-Read for L2 names.
  • Multicall3 - batches ENS Registry owner, BaseRegistrar ownerOf/nameExpires, and NameWrapper getData into a single eth_call.

Bulk Requests

The POST /whois/bulk endpoint accepts up to 500 names. Indexed names are served directly from the database. Names needing live resolution are batched efficiently:

  • Individual Universal Resolver calls run in parallel (not Multicall3, because CCIP-Read requires per-call client handling)
  • All ownership lookups are batched into a single Multicall3 call
  • Each name costs 1 API credit

Response Shape

All domain responses use a consistent flat structure with grouped objects:

{
  "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",
  "source": "indexed"
}
  • ownership.owner is the effective owner (wrapper > registrar > registry)
  • Per-contract addresses are included when available
  • ownership.wrapper is only present when the domain is wrapped
  • When wrapped, a wrapper object is included with a fuses field
  • source is only present when the response contains domain data

Resolver Status

The resolver object describes the domain's on-chain resolver:

StatusMeaningFields
indexedResolver is one we index - data served from the indexerchain, address
unindexedResolver is not one we index - data resolved live on-chainaddress
noneNo resolver set - domain resolves via ENSIP-10 on its parent-

Availability Status

The availability_status field is always a string:

ValueMeaning
registeredActive domain with expiry in the future (or no expiry)
graceExpired within the last 90 days - original owner can still renew
premiumExpired 90-111 days ago - anyone can re-register at a declining premium
availableAvailable for registration at base price
unknownCannot determine - name is on a chain this indexer doesn't cover, or is a subname

Errors

All error responses use a structured format:

{
  "error": {
    "code": "INVALID_NAME",
    "message": "Invalid domain name"
  }
}
CodeHTTP StatusMeaning
INVALID_NAME400Domain name is missing, too long, or cannot be normalised
INVALID_ADDRESS400Ethereum address is not valid
INVALID_HASH400Hash is not a valid 0x-prefixed 64-character hex string
INVALID_PARAMETER400A query parameter has an invalid value
MISSING_PARAMETER400A required parameter was not provided
LIMIT_EXCEEDED400Request exceeds the maximum allowed items
LIVE_RESOLUTION_FAILED502On-chain resolution was required but the RPC call failed
INDEXER_UNREACHABLE502The API proxy could not reach any indexer
NO_INDEXERS_AVAILABLE503No synced indexers are currently available
INTERNAL_ERROR500Unexpected server error

Pagination

Endpoints marked Paginated accept these query parameters:

NameTypeDescription
pageintegerPage number (default 1, 25 results per page)
sort_bystringSort field (endpoint-specific, see each endpoint for allowed values)
sort_dirstringasc (default) or desc

Paginated responses include total, page, and page_size fields.