Three surfaces, one contract.
The API is the product. The CLI follows its operation table, and MCP exposes the operations an agent can perform. Steps that require a person are marked below. This page is generated from the operation table and the MCP policy.
Manual installation
Authentication
One bearer token, issued by whydot login and stored by the CLI. It is shown once and never retrievable afterwards. There is no password and no web login form.
Spending money is explicit
Every call that can spend requires an idempotency key you choose. Send the same key twice and you get the first answer back, not a second domain and a second charge. The registrar's own API accepts no such token, so this is the only thing standing between a retry and a double purchase — which is why it is required rather than optional.
Operations
| Route | What it does | Scope |
|---|---|---|
| POST /v1/tenants | Create an account and its first API key. writes | no key |
| GET /v1/keys | List the API keys on this account, by prefix. | keys:manage |
| POST /v1/keys | Issue a new API key. writes | keys:manage |
| DELETE /v1/keys/:id | Revoke an API key immediately. writes | keys:manage |
| GET /v1/domains | List the domains in this account. | domains:read |
| GET /v1/domains/:id | Fetch one domain by id or by name. | domains:read |
| POST /v1/domains | Track a domain in this account. Spends nothing; registration is a separate call. writes | domains:write |
| POST /v1/registrant-contacts | Create a registrant contact for this account. writes | domains:write |
| GET /v1/availability | Is a domain available, and what would it cost. | domains:read |
| GET /v1/prices | Per-TLD registration prices. | domains:read |
| POST /v1/domains/:id/register | Register a tracked domain with the registrar. Spends money; requires an idempotency key. writes | domains:write |
| GET /v1/domains/:id/dns | The managed DNS records of a domain's zone, and what cannot be modelled. | dns:read |
| POST /v1/domains/:id/dns/plan | What would change if these records were the zone's whole contents. Writes nothing. | dns:read |
| POST /v1/domains/:id/dns/apply | Apply a plan from the plan route. Deleting needs allowDelete on THIS request. writes | dns:write |
| POST /v1/dns-connections | Connect the customer's own Cloudflare account. The token is proved, then sealed. writes | dns:write |
| GET /v1/dns-connections | The connected DNS provider accounts. Never the credentials. | dns:read |
| POST /v1/dns-connections/:id/verify | Ask the provider again whether the stored credential still works. writes | dns:write |
| POST /v1/dns-connections/:id/archive | Retire a connection. Refused while a live zone is served through it. writes | dns:write |
| POST /v1/domains/:id/dns/zone | Serve this domain's DNS through a connection. Creates the zone and reports the nameservers. writes | dns:write |
| POST /v1/domains/:id/dns/zone/delegate | Point the registrar at the serving zone's nameservers. Populate the zone first. writes | dns:write |
| POST /v1/domains/:id/dns/zone/release | Stop serving this domain's DNS through a connection. The zone at the provider is left alone. writes | dns:write |
| GET /v1/domains/:id/dns/zone | Where this domain's DNS is served, now and before. The serving row is first. | dns:read |
| POST /v1/labels | Create a label for this account. writes | domains:write |
| GET /v1/labels | Every label in this account. | domains:read |
| PATCH /v1/labels/:id | Rename or recolour a label. An omitted field is left alone; an explicit null clears it. writes | domains:write |
| DELETE /v1/labels/:id | Delete a label and every attachment of it. The things it was attached to are untouched. writes | domains:write |
| POST /v1/labels/:id/attachments | Attach a label to a domain, an address or a template, or detach it. Idempotent. writes | domains:write |
| GET /v1/domains/:id/labels | The labels on one domain. | domains:read |
| GET /v1/registrant-contacts/:id/labels | The labels on one registrant address. | domains:read |
| GET /v1/dns-templates/:id/labels | The labels on one DNS record template. | dns:read |
| GET /v1/registrant-contacts | Every registrant address in this account. Retired ones only when asked for. | domains:read |
| GET /v1/registrant-contacts/:id | One registrant address. | domains:read |
| PATCH /v1/registrant-contacts/:id | Edit an address. Refused once a domain has been registered under it. writes | domains:write |
| POST /v1/registrant-contacts/:id/default | Make this the address new registrations use. Exactly one account default. writes | domains:write |
| POST /v1/registrant-contacts/:id/archive | Retire an address. Never deleted: a registered domain still points at it. writes | domains:write |
| POST /v1/dns-templates | Store a reusable set of records. Checked whole: every placeholder must be declared. writes | dns:write |
| GET /v1/dns-templates | Every template in this account, with its records and variables. | dns:read |
| GET /v1/dns-templates/:id | One template, whole. | dns:read |
| POST /v1/dns-templates/:id/archive | Archive a template. Never deleted: a record of having applied it must still resolve. writes | dns:write |
| POST /v1/domains/:id/dns/templates/:templateId/plan | What applying this template to this domain would change. Writes nothing. | dns:read |
| GET /v1/billing/terms | The Terms version a top-up requires, and whether this account has accepted it. | billing:read |
| POST /v1/billing/terms | Record that the person accepted the Terms. Required once before a top-up. writes Person only · API/CLI | billing:write |
| GET /v1/billing | The account balance and its ledger. Every charge and credit, with its cause. | billing:read |
| POST /v1/billing/topup | Start a top-up. Returns a Checkout URL for a human to open; no card touches this API. writes | billing:write |
MCP
Point your agent at whydot-mcp for the agent operations above, with the same scopes. Terms acceptance is a human step through the API or CLI; it is not an MCP tool. An agent can read Terms status to know when your action is needed.