Resolve any .htr name
A free, read-only REST API for name resolution and profile lookups on the Hathor Network.
Names are case-insensitive and accepted with or without .htr. Responses echo the canonical form.
ETag + If-None-Match → 304application/problem+jsonX-Request-Id on uncached responsesRate-limit state is reported in RateLimit-* headers on uncached responses; CDN-cached responses omit them and don't count against the limit. 429s include Retry-After.
Forward (name → address) and reverse (address → name) resolution.
Resolve a name to an address
Strict forward resolution: returns the resolving address only while the name is active. Expired names return 404 NAME_EXPIRED, so a 200 is always safe to send funds to.
curl https://thoth.id/api/v1/names/jackal.htr/addressconst res = await fetch("https://thoth.id/api/v1/names/jackal.htr/address");
if (!res.ok) {
const problem = await res.json(); // application/problem+json
throw new Error(`${problem.code}: ${problem.detail}`);
}
const data = await res.json();{
"name": "jackal.htr",
"address": "WjcD1MGLi1TS8pH1JRcYzd9bJ1YPejVRtH"
}Reverse-resolve an address to its primary name
Returns the primary name the address set as manager, if that name is active. verified is true when the name also resolves forward to the same address; only display unverified names with care.
curl https://thoth.id/api/v1/addresses/WSoSypkhE8i3iZxZvXCq91vWDwr8wKFVVq/nameconst res = await fetch("https://thoth.id/api/v1/addresses/WSoSypkhE8i3iZxZvXCq91vWDwr8wKFVVq/name");
if (!res.ok) {
const problem = await res.json(); // application/problem+json
throw new Error(`${problem.code}: ${problem.detail}`);
}
const data = await res.json();{
"address": "WSoSypkhE8i3iZxZvXCq91vWDwr8wKFVVq",
"name": "biu.htr",
"verified": true
}Resolve many names and addresses at once
Resolve up to 50 items in total (names + addresses) in one request. Results keep the input order and carry a per-item error (same codes as problem responses). Node outages fail the whole request with 502/504. Counts as 1 + ⌊items / 5⌋ rate-limit units. Not cached.
curl -X POST https://thoth.id/api/v1/batch/resolve \
-H "Content-Type: application/json" \
-d '{"names":["jackal.htr","thoth","nobody-here"],"addresses":["WSoSypkhE8i3iZxZvXCq91vWDwr8wKFVVq"]}'const res = await fetch("https://thoth.id/api/v1/batch/resolve", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({"names":["jackal.htr","thoth","nobody-here"],"addresses":["WSoSypkhE8i3iZxZvXCq91vWDwr8wKFVVq"]}),
});
if (!res.ok) {
const problem = await res.json(); // application/problem+json
throw new Error(`${problem.code}: ${problem.detail}`);
}
const data = await res.json();{
"names": [
{
"input": "jackal.htr",
"name": "jackal.htr",
"address": "WjcD1MGLi1TS8pH1JRcYzd9bJ1YPejVRtH",
"error": null
},
{
"input": "thoth",
"name": "thoth.htr",
"address": "WSoSypkhE8i3iZxZvXCq91vWDwr8wKFVVq",
"error": null
},
{
"input": "nobody-here",
"name": "nobody-here.htr",
"address": null,
"error": {
"code": "NAME_NOT_FOUND",
"message": "nobody-here.htr is not registered."
}
}
],
"addresses": [
{
"input": "WSoSypkhE8i3iZxZvXCq91vWDwr8wKFVVq",
"address": "WSoSypkhE8i3iZxZvXCq91vWDwr8wKFVVq",
"name": "biu.htr",
"verified": true,
"error": null
}
]
}Name records, status, availability and fee quotes.
Get a name record
Full on-chain record: status, resolving address, manager, owner, NFT and expiration. Names whose grace period has lapsed return 404 NAME_NOT_FOUND.
curl https://thoth.id/api/v1/names/test.htrconst res = await fetch("https://thoth.id/api/v1/names/test.htr");
if (!res.ok) {
const problem = await res.json(); // application/problem+json
throw new Error(`${problem.code}: ${problem.detail}`);
}
const data = await res.json();{
"name": "test.htr",
"label": "test",
"status": "active",
"resolvingAddress": "WSoSypkhE8i3iZxZvXCq91vWDwr8wKFVVq",
"manager": "WSoSypkhE8i3iZxZvXCq91vWDwr8wKFVVq",
"owner": "WSoSypkhE8i3iZxZvXCq91vWDwr8wKFVVq",
"nftDeposited": true,
"tokenUid": "00f34fabeb19991b7981c3e4439d713856a9ee9ff0725ed6023c335da52eb5e0",
"expiresAt": "2027-02-10T14:56:07.000Z",
"expiresAtUnix": 1802271367,
"gracePeriodEndsAt": "2027-03-12T14:56:07.000Z",
"gracePeriodEndsAtUnix": 1804863367
}Check availability and quote the fee
Whether anyone can register the name right now, its status, and the registration fee for one period. Unregistered names return 200 with available: true.
curl https://thoth.id/api/v1/names/abc/availabilityconst res = await fetch("https://thoth.id/api/v1/names/abc/availability");
if (!res.ok) {
const problem = await res.json(); // application/problem+json
throw new Error(`${problem.code}: ${problem.detail}`);
}
const data = await res.json();{
"name": "abc.htr",
"label": "abc",
"available": true,
"status": "available",
"expiresAt": null,
"expiresAtUnix": null,
"gracePeriodEndsAt": null,
"gracePeriodEndsAtUnix": null,
"fee": {
"currency": "HTR",
"decimals": 2,
"baseFee": 1,
"multiplier": 20,
"amount": 20,
"formatted": "0.20",
"periodDays": 365
}
}Profile records attached to a name (avatar, description, socials).
Get profile records
Profile records of a registered name: typed well-known keys, a displayable avatarUrl, and the raw key/value map as stored on-chain.
curl https://thoth.id/api/v1/names/test.htr/profileconst res = await fetch("https://thoth.id/api/v1/names/test.htr/profile");
if (!res.ok) {
const problem = await res.json(); // application/problem+json
throw new Error(`${problem.code}: ${problem.detail}`);
}
const data = await res.json();{
"name": "test.htr",
"status": "active",
"avatarUrl": "https://abeesgqcvlollxwc.public.blob.vercel-storage.com/domain-images/test-hsdSU0hh1L5qF21L54mTn4QMzHCb1u.htr/avatar",
"records": {
"avatar": "https://abeesgqcvlollxwc.public.blob.vercel-storage.com/domain-images/test-hsdSU0hh1L5qF21L54mTn4QMzHCb1u.htr/avatar",
"description": null,
"website": null,
"email": null,
"twitter": null,
"github": null,
"discord": null,
"telegram": null
},
"raw": {
"avatar_link": "https://abeesgqcvlollxwc.public.blob.vercel-storage.com/domain-images/test-hsdSU0hh1L5qF21L54mTn4QMzHCb1u.htr/avatar"
}
}Names managed by an address.
List names managed by an address
All names the address manages (max 100 per address, enforced on-chain) plus its primary name. An address with no names returns an empty list.
curl https://thoth.id/api/v1/addresses/WjcD1MGLi1TS8pH1JRcYzd9bJ1YPejVRtH/namesconst res = await fetch("https://thoth.id/api/v1/addresses/WjcD1MGLi1TS8pH1JRcYzd9bJ1YPejVRtH/names");
if (!res.ok) {
const problem = await res.json(); // application/problem+json
throw new Error(`${problem.code}: ${problem.detail}`);
}
const data = await res.json();{
"address": "WjcD1MGLi1TS8pH1JRcYzd9bJ1YPejVRtH",
"primaryName": "jackal.htr",
"names": [
"jackal.htr"
],
"total": 1
}Contract configuration, health and API description.
Get contract configuration
Fee tiers, profile limits, grace period and API limits. Changes rarely; cache it on your side too.
curl https://thoth.id/api/v1/configconst res = await fetch("https://thoth.id/api/v1/config");
if (!res.ok) {
const problem = await res.json(); // application/problem+json
throw new Error(`${problem.code}: ${problem.detail}`);
}
const data = await res.json();{
"domain": "htr",
"network": "testnet-india",
"contractId": "00001f87ed606c28465afac15fe3805736993f77d4cc83da026531e120469d73",
"blueprintId": "000000009108a3ab3c24297df5e33679177fb8a051c133bcad467a8a23bf0dd3",
"contractVersion": "1.0.0",
"gracePeriodDays": 30,
"fees": {
"currency": "HTR",
"decimals": 2,
"baseFee": 1,
"periodDays": 365,
"tiers": [
{
"minLength": 3,
"maxLength": 3,
"multiplier": 20,
"amount": 20,
"formatted": "0.20"
},
{
"minLength": 4,
"maxLength": 4,
"multiplier": 10,
"amount": 10,
"formatted": "0.10"
},
{
"minLength": 5,
"maxLength": null,
"multiplier": 1,
"amount": 1,
"formatted": "0.01"
}
]
},
"limits": {
"nameMinLength": 3,
"nameMaxLength": 80,
"maxProfileEntries": 20,
"maxProfileKeyLength": 50,
"maxProfileValueLength": 1000,
"maxTotalProfileSize": 10000,
"maxTokenSymbolLength": 5,
"maxManagedNamesPerAddress": 100
},
"api": {
"version": "1.0.0",
"batchMaxItems": 50,
"rateLimit": {
"limit": 120,
"windowSeconds": 60
}
}
}Health check
API liveness plus reachability of the Hathor node(s). Returns 503 when no node is reachable. Not rate limited, never cached.
curl https://thoth.id/api/v1/healthconst res = await fetch("https://thoth.id/api/v1/health");
if (!res.ok) {
const problem = await res.json(); // application/problem+json
throw new Error(`${problem.code}: ${problem.detail}`);
}
const data = await res.json();{
"status": "ok",
"apiVersion": "1.0.0",
"time": "2026-09-23T21:00:00.000Z",
"upstream": {
"primary": {
"reachable": true,
"latencyMs": 142,
"network": "testnet-india",
"nodeVersion": "0.71.0"
},
"fallback": null
}
}Every error is an RFC 9457 problem with a stable code. Branch on code, not on detail.
The name is not a valid .htr name (3-80 chars, a-z, 0-9 and single inner hyphens).
The value is not a valid base58 Hathor address.
The request body or query string failed validation. See errors for details.
The batch request contains more items than allowed.
The name is not registered, or its registration and grace period have lapsed.
The name is registered but expired (in its grace period), so it does not resolve.
The address has no primary name, or its primary name is no longer active.
No API resource exists at this path.
The HTTP method is not supported on this resource. See the Allow header.
The request body exceeds the maximum accepted size.
Request bodies must be sent as application/json.
The client exceeded its rate limit. Retry after Retry-After seconds.
An unexpected error occurred. Include X-Request-Id when reporting it.
The Hathor node returned an error or an unexpected response.
The Hathor node did not respond in time. Safe to retry.
Data comes from the chain, so responses are cached briefly at the edge. Clients should honour Cache-Control and revalidate with If-None-Match.
{
"type": "https://thoth.id/developers#error-name-not-found",
"title": "Name not found",
"status": 404,
"detail": "nobody-here.htr is not registered.",
"instance": "/api/v1/names/nobody-here.htr/address",
"code": "NAME_NOT_FOUND",
"requestId": "5b0f3c1e-8d43-4c55-9d0a-2a57d1c2e9f1"
}