Domain
Package domain is Hanzo Domains: search a name, see the price, buy it from your prepaid wallet.
Package domain is Hanzo Domains: search a name, see the price, buy it from your prepaid wallet.
| Base URL | https://api.hanzo.ai |
| Operations | 7 |
| Auth | Authorization: Bearer $HANZO_API_KEY |
Specification
HIP-1123 · Domain — Name Registration — Draft · read the specification →
/v1/domain is Hanzo Domains: search a name, see the price, buy it from the
org's prepaid balance. It is implemented in hanzoai/cloud at apps/domain,
reselling a wholesale registrar behind one interface and handing every new zone
to Hanzo DNS. This HIP states the purchase pipeline, where the money moves, and
what the ownership store is and is not.
Motivation
DNS manages records for a domain an org already controls; nothing acquired the
domain. Domains is the acquisition product, distinct on purpose
(apps/domain/domain.go:8-12): buy here, manage records there. The core is
transport-free — availability → price → authorize → register → provision-zone →
capture → record, orchestrated over four interfaces (Registrar, Biller, Zones,
Store) — so the policy is unit-testable with no HTTP, registrar or billing
backend (apps/domain/domain.go:13-18).
Specification
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.
§1 The store, honestly
The registrar account is the authority on what is registered. What domain owns
is the domain↔org Holding row it writes on a successful purchase, and its
default Store is in-memory (apps/domain/store.go:9-13, MemStore) —
sufficient for a single-process deployment, swapped behind the same interface
for a durable one in a multi-replica deployment. A restart loses the
projection, never the registration.
§2 The addresses
Every route is under /v1/domain (manifest/apps.go:162) and every operation
is typed: health, search, availability, the org's holdings, register, renew,
transfer (apps/domain/mount.go:24-32).
§3 The price and the debit
The price a customer sees carries the markup applied in exactly one place
(apps/domain/pricing.go:11-16): multiplier over wholesale, a minimum absolute
margin, rounded up to whole cents, never below cost. A purchase is two-phase
through the Biller (apps/domain/domain.go:57-71): Authorize refuses with 402
when the prepaid balance cannot cover the marked-up cents — before the
registrar is touched — and Capture debits after the registrar succeeds. The
Biller is cloud's ResourceMeter (apps/domain/mount.go:56, mount.go:159-186):
Gate is the authorization, MeterUsage under the domain.register meter is the
capture, so the debit lands on the org's ledger through the same money plane as
every other charge. The plugin declares cloud.Metered
(plugin/domain/main.go:29) and the capability is in spend.go's metered
list (spend.go:299): every purchase moves money, in integer cents.
§4 Tenancy
Every read and every mutation resolves the org from the validated principal
(principal.Acting, apps/domain/mount.go:417, HIP-0026); the org owns the
purchase and is the ledger the charge lands on. Renew and transfer on a domain
the org does not hold answer ErrNotOwned; a purchase of a name the org
already holds answers ErrAlreadyOwned (apps/domain/domain.go:29-40).
§5 Money, events, telemetry
The metered facts are in §3. domain publishes no events on the bus, and emits nothing to observability beyond the request span every route gets.
§6 Stage
domain is ga: acquiring a name for a deployment is developer tooling in the
agentic-OS core, not a vertical application.
§7 Upstream
domain derives from no forked code. It implements the name.com Core API v4 wire
as its wholesale registrar client (apps/domain/namecom), and hands zones to
hanzoai/dns. Registrar credentials arrive as operator-injected env from the
platform secret store, never hard-coded (apps/domain/mount.go:34-37).
Rationale
Two-phase billing with authorize-before-registrar, rather than charge-then-buy
or buy-then-charge, is the only order in which neither party is left holding
the other's failure: a refused balance costs the registrar nothing, and a
registrar failure costs the customer nothing. Capture deliberately takes no
idempotency key named after the domain (apps/domain/domain.go:63-70): a renew
and a lapse-and-rebuy are distinct acts, and keying the ledger by the name
silently collapsed them into one charge.
Security Considerations
The wrong implementation spends someone else's balance or sells below cost.
The org on a purchase comes only from the validated principal, so a caller
cannot name the ledger to debit. The markup floor is clamped at 1 and the sell
price at cost (apps/domain/pricing.go:23-34), so no configuration sells below
wholesale. A deployment with no registrar credential fails every purchase
closed with 503 (ErrNotConfigured), and the credential itself lives in the
secret store, reachable only as injected env.
Four surfaces
| Surface | Reaches this capability as | Coverage |
|---|---|---|
| REST | domain at its own prefix | 7 operations |
| CLI | hanzo domain … | 7 of 7 |
| SDK | DomainApi in every published client | 7 methods |
| MCP | tool domain on https://api.hanzo.ai/v1/mcp | 7 operations, 3 under the document's own id — ask describe for the rest |
Quickstart
export HANZO_API_KEY=sk-... # console.hanzo.ai → API keysThen the first call — a read that needs nothing but the key. GET /v1/domain/health, operation get_domain_health:
hanzo domain healthimport { Configuration, DomainApi } from 'hanzoai';
const api = new DomainApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.getDomainHealth();from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import DomainApi
client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = DomainApi(client).get_domain_health()cfg := cloud.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := cloud.NewAPIClient(cfg)
resp, _, err := client.DomainAPI.GetDomainHealth(context.Background()).Execute()
if err != nil {
return err
}use hanzo_cloud::apis::{configuration::Configuration, domain_api};
let mut cfg = Configuration::new();
cfg.bearer_access_token = std::env::var("HANZO_API_KEY").ok();
let result = domain_api::get_domain_health(&cfg, Default::default()).await?;import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.DomainApi;
ApiClient client = new ApiClient();
client.setRequestInterceptor(b -> b.header("Authorization", "Bearer " + System.getenv("HANZO_API_KEY")));
var result = new DomainApi(client).getDomainHealth();curl https://api.hanzo.ai/v1/domain/health \
-H "Authorization: Bearer $HANZO_API_KEY"Tool domain, op get_domain_health — POST the JSON-RPC envelope to https://api.hanzo.ai/v1/mcp.
curl -X POST https://api.hanzo.ai/v1/mcp \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "domain",
"arguments": {
"op": "get_domain_health",
"input": {}
}
}
}'Answers 200 with object — ok.
Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/domain/availability | Checks exact names rather than searching for them, and answers the same quote shape search does — purchasable, premium, first-term and renewal price… |
GET /v1/domain/domains | Is the domains your org has bought here, newest registration first, each carrying the name, when it was registered, when it expires, what the org… |
GET /v1/domain/health | Reports registrar reachability honestly: ok only when the wholesale credentials are present AND name.com accepted them on a live call made while you… |
POST /v1/domain/register | Buys a domain for your org and answers the ownership record together with the quote it was bought at. |
POST /v1/domain/renew | Extends a domain your org already owns and answers the updated record with its new expiry alongside what was paid. |
GET /v1/domain/search | Finds names built from the keyword q, plus the registrar's alternate-TLD suggestions, and answers a quote for each: the name, whether it is… |
POST /v1/domain/transfer | Moves a domain you own at another registrar onto your org here, using its authCode, and answers the same record-plus-quote a purchase does. |
How is this guide?