Actions
5 actions, callable from the panel, the command palette and the API as
POST /api/v1/a/<id>. Internal actions used between modules are not listed.
| Action | What it does | Risk | Preview |
|---|---|---|---|
domains.create | Add a domain | low | |
domains.list | List domains (read) | low | No dry run |
domains.get | Get a domain (read) | low | No dry run |
domains.verify | Check domain ownership | low | No dry run |
domains.delete | Remove a domain | high |
Permissions and limits
Permissions
domains.manageAdd and remove domainsdomains.viewView domains
Plan limits
domains.countDomains (incl. addon and parked)domains.subdomainsSubdomains
Engineering notes
Generated from modules/domains/docs.md at build d90e9e2. These are the notes the engineers keep
next to the code: precise, technical, and honest about what is not done yet.
Owns domain names and who they belong to; web, dns, mail, ssl and cdn reference domains by id and react to
domains.created / domains.deleted through manifest subscriptions (ADR 0011 §3).
Patterns on top of modules/accounts (copy these):
- Dry run:
domains.createanddomains.deletedeclaredry_run: true; whenc.DryRunthe handler validates everything, changes nothing and returns amodule.Plan(POST /api/v1/a/domains.create?dry_run=1). - Inverse:
domains.createdeclaresinverse: domains.deletefor the change timeline / assistant rollback. - Resources + scoped grants:
resources: [{kind: domain}]; sub-users see/manage only domains granted to them (c.Can("domains.view", "domain", id)). - Durable subscription:
subscriptions: [{event: accounts.deleted, action: domains.on_account_deleted}]; the action isinternal: true(hidden from the API) and idempotent (a redelivery finds nothing left). - Cross-module checks: a reseller's access to an account is checked by calling
accounts.get(which applies the subtree rule) instead of duplicating it. - Names are normalised with IDNA (UTS-46) -> stored as lower-case punycode, shown as Unicode.
Verified in the lab (2026-10-09): dry run returns a plan and creates nothing; Bücher.lab.test stored as
xn--bcher-kva.lab.test; invalid label rejected; internal action -> 404 from the API; deleting the account removed
its domain via the event within seconds.
Next: UI; per-plan auto-provisioning (website + zone + mail domain on create) lives in those modules' subscriptions.