ZeqDNS — node-scoped names and computed addresses
Two different subsystems have been described together under one name, and they behave very differently. Keep them apart:
| The name registry | The computed-address layer | |
|---|---|---|
| Routes | /api/hz/* | /api/dns/* |
| What it holds | hz_names rows in Postgres, per node | an in-process address map |
| Live on 2026-09-03 | answering, priced, node-scoped | totalRecords: 0 on every node probed |
| Use it? | yes | not yet — see below |
An earlier version of this page documented human-legible names against /api/dns/*. That was wrong
on both halves: names have never lived there, and /api/dns/* resolves computed addresses out of a
registry that is currently empty.
The name registry — /api/hz/*
Four routes, verified against https://zeq.me/openapi.json:
| Route | Auth | Does |
|---|---|---|
GET /api/hz/check/:label | public | availability + price |
GET /api/hz/resolve/:name | public | name → owner ZID (and machine) |
GET /api/hz/names/:zid | owner-gated | the names a ZID owns |
POST /api/hz/register | bearer | claim a name |
curl -s https://zeq.me/api/hz/check/alice
# {"ok":true,"label":"alice","name":"alice.hz","available":true,"price_credits":121}
curl -s https://zeq.me/api/hz/resolve/alice.hz
# {"ok":false,"error":"not_found","name":"alice.hz"}
curl -s https://zeq.me/api/hz/names/ZEQTEST
# {"ok":false,"error":"Authentication required.","code":"UNAUTHORIZED","redirect":"/auth/"}
Names are node-scoped, and .hz is only one node's suffix
This is the part most likely to surprise you. Every node sells names under its own suffix, and
.hz belongs to zeq.me. Ask the same question of five nodes and you get five different names:
for h in zeq.me zeqproof.com zeq.dev hulyas.org zeqond.com; do
printf '%-16s ' "$h"; curl -s "https://$h/api/hz/check/alice" \
| python3 -c "import sys,json;print(json.load(sys.stdin)['name'])"
done
zeq.me alice.hz
zeqproof.com alice.zeqproof
zeq.dev alice.zeqdev
hulyas.org alice.hulyasorg
zeqond.com alice.zeqond
nodeTld() derives the suffix from the node's own public origin, falling back to hz only when the
origin is unpinned. The reason is stated in the source and is worth repeating, because it explains
why there is no reconciliation protocol here: uniqueness is a local UNIQUE(label, tld)
constraint, so the cross-node claim race is structurally impossible. No two nodes can ever sell the
same name, so there is nothing to reconcile and nothing to refund.
Price is a function of the framework's own economy, not of the node: 121 credits per year on every
node sampled.
Paid in the framework's own economy
A name is bought with compute credits or by burning owned envelopes. There is no external money in this path. The register body, from the live OpenAPI spec:
{ "label": "alice", "years": 1,
"payment": { "method": "credits" },
"machine_id": "optional — bind the name to one of your machines" }
{ "label": "alice",
"payment": { "method": "envelopes", "envelope_ids": ["…", "…"] } }
Registration is first-come-first-served on the local unique constraint. Each registration is written
to the node's entangled state (the audit trail gives ordering) and sealed with a deterministic
nameRecordHash over label, tld, ownerZid, registeredZeqond and origin — any node can
recompute it from the public fields, so a registry row cannot be silently altered.
Resolution federates exactly one hop
The hz_names table is per-node, like every other private table. On its own, a name registered on
one node would neither resolve nor read as taken on the others. lib/hzFederation.ts closes the read
half:
- a resolve or availability question fans out to the sibling nodes;
- peer calls carry the header
x-hz-federated: 1, and a node serving a request that already has that header answers from its local database only — so federation is exactly one hop and cannot loop; - every peer fetch is time-boxed, and a failure is swallowed: a peer being down degrades to "not found there", never an error to you;
- results are cached in-process, hits longer than misses.
A resolved name comes back with its home origin, so you learn where the name lives as well as who owns it:
{ "ok": true, "name": "…", "owner_zid": "ZEQ…", "machine_id": "…",
"registered_zeqond": 0, "expires_zeqond": 0, "origin": "https://…" }
The source is candid that this is the read side of an incremental plan: it gives global resolution today and shrinks — but does not yet fully close — the registration race. Node-scoped suffixes are what make that acceptable in the meantime.
The computed-address layer — /api/dns/*
The other idea under the ZeqDNS name is genuinely different: replace a lookup with a computation. A domain is hashed, its first bytes become a seed, and the seed is run through the framework's own constants to land on an address:
That arithmetic is real and deterministic, and you can watch it run:
curl -s -X POST https://zeq.me/api/dns/resolve \
-H 'content-type: application/json' -d '{"domain":"api.zeq.dev"}'
# {"error":"Domain not registered","code":"NOT_FOUND",
# "domain":"api.zeq.dev","address":"00000000f0d05000"}
The address is computed. What is missing is anything to look up:
for h in zeq.me zeqproof.com zeq.dev; do printf '%-16s ' "$h"; curl -s "https://$h/api/dns/status"; echo; done
# zeq.me {"totalRecords":0,"activeRecords":0,"domains":0,"zeqond":…}
# zeqproof.com {"totalRecords":0,"activeRecords":0,"domains":0,"zeqond":…}
# zeq.dev {"totalRecords":0,"activeRecords":0,"domains":0,"zeqond":…}
/api/dns/* todayThe registry behind it is an in-process map, empty on every node we probed, and it is not where
.hz names live. POST /api/dns/register also carries no security requirement in the published
OpenAPI spec, where POST /api/hz/register requires a bearer token — another reason to treat the
/api/hz/* surface as the real one and this one as unfinished.
Read next
- Mesh & self-hosting — the transports names ride on, and standing up your own node (which brings its own suffix with it).
- Nodes and node-locality — the eighteen nodes, and which suffix belongs to which.
- ZeqID — identity derived by computation, the same idea applied to people.