# Domains

Every domain in the workspace is one entry, keyed by its root (`GET /api/domains`), with one plain-word state and the next step. www is served together with the apex.

## Buy, connect or transfer?

| You want to | Do this |
| --- | --- |
| A new name | Buy it here. It is paid from the Balance, and can connect to a project in the same call. |
| Use a name you own elsewhere | Connect it: add it to a project and switch its nameservers to the two Harakumo gives you. |
| Move a name you own to Harakumo | Transfer it in with its auth code; the price includes a year of renewal. |

## Buying

1. Search: `GET /api/domains/search?q=myshop` (or `myshop.org` to search that name first). Each result has `available` (null means it could not be checked — not taken), `sellable`, `price_cents`, `renewal_price_cents` and, for a name you can buy, `lock_lifts_at`.
2. Buy: `POST /api/domains/registrations { domain, projectId? }`. The live price is checked again; if it is above the price you saw (`expectedPriceCents`) nothing happens.
3. With a projectId the domain connects automatically and goes live within minutes to hours as DNS settles; the owner gets an email when it is live.

- Endings sold today: .com, .dev, .app, .io, .ai, .cloud. Automatic purchases above $100 a year are refused.
- Every domain is paid from the Balance at the price shown before you buy, on every plan. There is no cap on how many you buy — each is paid for.
- A Balance too small answers 402 `insufficient_balance` with the shortfall and where to add funds; nothing is charged. The card on file is charged only when a signed-in owner asks (`payWithCard: true`).
- Purchases count toward the workspace's monthly spending limit, if one is set.
- Harakumo registers the domain through its registrar account and manages it for your workspace. Harakumo is the registrant of record (the registration lists Harakumo, not you, as the holder, and your details are not sent to the registrar); your workspace is the record that the domain is yours, and you can transfer it out to hold it in your own name. Registries lock a domain for 60 days after registration or transfer.
- That lock means a domain you buy cannot move to another registrar until `lock_lifts_at`, 60 days after the purchase; it works as usual meanwhile. The buy dialog says so before you pay, and search results, the dry run (`dryRun: true`), the purchase answer and every registration carry the date.
- Prices are in US dollars and do not include taxes such as sales tax, VAT or GST.

## Connecting a domain you own

1. Add it to a project: `POST /api/projects/:id/domains { hostname, connect: true }`. Harakumo checks no other workspace owns that root domain. Adding the domain itself (example.com) serves www.example.com too, unless www is added as a hostname of its own; a subdomain serves only itself.
2. Harakumo copies the records your domain answers with today (email included) into its DNS, and returns two nameservers. Review the copied records in DNS before switching.
3. Set those nameservers at your registrar. The domain goes live on its own within about 10 minutes of the change being visible — there is nothing to press.

> Switching nameservers moves all DNS for the root domain, even when you only connect a subdomain. Connecting by CNAME is not available yet.

## DNS records

Any domain whose DNS Harakumo hosts — bought, transferred in or connected by nameservers — has editable records at `/api/domains/zones/{root}/dns` (GET, POST, PATCH, DELETE), and the same DNS panel under Manage on the Domains page. Record types: A, AAAA, CNAME, MX, TXT and NS. SRV and CAA are not supported yet.

**Add an MX record**

```bash
curl -X POST https://harakumo.com/api/domains/zones/myshop.com/dns \
  -H "Authorization: Bearer $HARAKUMO_API_KEY" -H "content-type: application/json" \
  -d '{ "type": "MX", "name": "@", "content": "mail.example.net", "priority": 10 }'
```

> Want email from this domain? Mail → Sending domains sets it up (automatically when its DNS is here).

## Renewal

- Bought domains auto-renew by default (`autoRenew: false` at purchase to opt out). Renewal happens in the last 7 days before expiry, at `renewal_price_cents`, paid from the Balance. Warnings go out 30, 7 and 1 days before.
- The card covers a renewal only if the owner, signed in, turned on card backup (`autoRenewCard: true` at purchase, or `PATCH { action: "auto-renew", enabled: true, cardBackup: true }`). Renewals set up by developers, API keys or transfers pay from the Balance only.
- A domain past expiry is marked expired; renewing it within 30 days points it back at its DNS.
- Every renewal is paid at its renewal price, on every plan. The renew answer says what was charged and where from (`chargedCents`, `paidFrom`), and the expiry email quotes the price of an automatic renewal.

## Stop serving, DNS only, other nameservers

- `DELETE /api/domains/registrations/:id` (or `PATCH { action: "detach" }`) stops serving the project. DNS, email and renewal stay.
- `PATCH { action: "host-dns" }` hosts DNS here without a project; `{ action: "set-nameservers", nameservers: [...] }` points the domain at another provider; `{ action: "connect", projectId }` serves another project.
- Deleting a project detaches its domains; bought domains stay in the workspace and keep renewing.

## Transfers

- In: unlock the domain at your current registrar, get its auth code, and `POST /api/domains/transfers { domain, authCode, dryRun: true }` to validate and quote without paying. Then send it again without dryRun (with `expectedPriceCents`). The Balance pays once; the transfer completes in 5–7 days.
- Out: `PATCH /api/domains/registrations/:id { action: "transfer-out" }`. The auth code arrives within five calendar days (usually sooner), by email and in the dashboard. The domain keeps working until it has moved.

## Limits

- Your own domains connected per plan: 2 hobby, 20 pro, 500 enterprise.
- Up to 10 new DNS zones per workspace a day, and 20 a day across all the workspaces one person owns. Connecting a domain the workspace already holds is never refused by either.
- Domain routes are workspace-wide: use a workspace key. Adding a hostname to a project (`/api/projects/:id/domains`) accepts that project's key.

## Reference

**SDK**

```js
const { results } = await hk.domains.search('myshop');     // [{ domain, available, sellable, price_cents, renewal_price_cents }]
const pick = results.find(r => r.available && r.sellable);
const { registration, paidFrom } = await hk.domains.register(pick.domain, { projectId: project.id, expectedPriceCents: pick.price_cents });
// 402 insufficient_balance → err.body.shortfall_usd, err.body.add_funds_url

await hk.domains.add(project.id, { hostname: 'app.myshop.dev' });   // a domain you own; connect: see REST
const { registration: one } = await hk.domains.registration(registration.id);   // renewal_price_cents, lock_lifts_at
const check = await hk.domains.transferIn('myshop.com', { authCode: 'EPP-CODE', dryRun: true });
```

**CLI**

```bash
harakumo domains buy myshop.com 12    # from the Balance; --card uses the card on file (owner)
```

**REST**

```http
GET    /api/domains                         # every domain, one per root, with state, next step and renewal
GET    /api/domains/search?q=myshop
POST   /api/domains/registrations           { "domain", "projectId"?, "expectedPriceCents"?, "autoRenew"?, "autoRenewCard"?, "payWithCard"? }
GET    /api/domains/registrations           # each with renewal_price_cents, lock_lifts_at and state
GET    /api/domains/registrations/:id       # one, with renewal_price_cents and lock_lifts_at
PATCH  /api/domains/registrations/:id       { "action": "connect", "projectId" } | "detach" | "host-dns" | "set-nameservers" | "renew" | "auto-renew" | "transfer-out" | "cancel-transfer-out"
DELETE /api/domains/registrations/:id       # stop serving (DNS and renewal stay)
POST   /api/projects/:id/domains            { "hostname", "connect": true }   # → nameservers
POST   /api/domains/:id                     # check a connected domain now (the sweep also does)
GET|POST|PATCH|DELETE /api/domains/zones/:root/dns
POST   /api/domains/transfers               { "domain", "authCode", "projectId"?, "dryRun"?, "expectedPriceCents"? }
GET    /api/domains/transfers · PATCH /api/domains/transfers/:id { "action": "check"|"replace-code"|"cancel" }
```
