{
  "schema": "leumas.docs.page/1",
  "id": "skill:leumas-studio#domains/hosting",
  "slug": "skills/leumas-studio/domains/hosting",
  "kind": "tools",
  "bucket": "skill",
  "title": "Hosting — what you can do",
  "name": "Hosting",
  "eyebrow": "what you can do",
  "chip": null,
  "summary": "<!-- GENERATED by ops/tools/skill-sync — do not edit by hand. Run pnpm skills:sync. -->",
  "keywords": [
    "leumas-studio",
    "fetch remember",
    "tabs focus",
    "skill-sync",
    "pnpm",
    "skills",
    "edit",
    "hand"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "<!-- GENERATED by ops/tools/skill-sync — do not edit by hand. Run `pnpm skills:sync`. -->\n\n# Hosting — what you can do\n\n`/admin/d/hosting` · pillar `imperium` · entitlement `imperium.hosting`\n\nBuild, publish and serve websites - pages, appearance, SEO, domains, DNS and edge rules.\n\n## Fetch this, never remember it\n\nThese change per install and per release. Call for them - a list written here would be a future lie.\n\n| What | Call | Why |\n|---|---|---|\n| render modes (what a site CAN be) | `list_modes` | `GET /index/modes` | grows every release, and a tenant can add its own `x-` modes - any list written down is a future lie |\n| one mode's exact data contract | `get_mode_schema` | the fields a mode accepts are its own; guessing them produces a payload that validates as empty |\n| theme presets | `GET /api/hosting/theme-presets` | presets and the custom-CSS capability key are read together, per install |\n| this operator's sites and their status | `list_my_sites` | `GET /api/hosting/sites` | never assume a site exists, and never assume its status - `draft` and `published` behave differently |\n| what this plan allows and how much is left | `GET /api/hosting/access` | returns the live capability plus used/remaining, so a quota refusal can be predicted instead of hit |\n\n## How to actually do things here\n\nWritten by hand, because the order and the traps are the part no file can derive.\n\n### Create a website and publish it\n\n1. `GET /api/hosting/access` first - it says whether this operator may host at all and how many sites are left.\n2. Choose the mode by CALLING, never from memory: `list_modes` for the catalog (each row carries a `group`: core, content, app, distribution, commerce), then `get_mode_schema(mode)` for the exact fields and a starter template.\n3. `POST /api/hosting/sites` with `{id, appName, mode, data}`. The `id` IS the subdomain. The site is created `draft`.\n4. Edit the home page with `set_site_content` ({appName, mode, data}); it updates the preview only.\n5. `save_site` persists. `save_site({publish: true})`, or `POST /api/hosting/sites/:id/publish`, takes it live.\n\n**Trap:** Publish re-validates the site data AND every page before it will go live - a draft may hold work in progress, a published site may not. A failure comes back as 400 `invalid_routes`, which is about a PAGE, not the home page.\n\n### Add pages to a site\n\n1. `list_pages` to see what is there. Paths are absolute: `/`, `/menu`, `/blog/:slug`.\n2. `add_page` with a `componentKey`. The usual one is `mode:<type>` - a whole render mode used as a page, whose `props` ARE that mode's data payload, so call `get_mode_schema(<type>)` first.\n3. `set_page_content` to edit one, `remove_page` to drop one. All three write immediately.\n4. Set `guard` to `public`, `auth` or `membership` to decide who may see it.\n\n**Trap:** [critical] The HOME page and every OTHER page are different lanes. Home is `config.mode` + `config.data` and needs `save_site`. Every other page is a route that persists on write. Sending `routes` or `components` to `PUT /api/hosting/sites/:id` answers 400 `use_route_lane`.\n\n### Restyle a site without touching its content\n\n1. `get_appearance` returns the current look AND the full contract - every field and its allowed values. Call it before writing.\n2. `list_theme_presets` names the whole-site looks a site can start from.\n3. `set_appearance` takes only what changes; pass `null` for a key to clear that key.\n4. `save_site` to persist - appearance is editor state until then.\n\n**Trap:** Every non-colour field is a closed enum, so an invented value is refused rather than approximated. `appearance: null` CLEARS the whole block and lets the tenant default show through; `undefined` leaves it alone. Never write `config.theme` - it is legacy and operator-only.\n\n### Point a host at something the user already runs\n\n1. This is the `target` axis, not the mode axis. Open the site editor's Serves tab.\n2. Pick the kind: `proxy` (an origin they run elsewhere), `bundle` (a build they upload to this site), `redirect` (send visitors somewhere else), `config` (the default - an Imperium page).\n3. For `proxy`, `POST /api/hosting/sites/:id/proxy-check` first - it is an SSRF-safe reachability probe.\n4. Write it with `PUT /api/hosting/sites/:id` carrying `{target: {...}}`.\n\n**Trap:** `static` and `server` targets, and a proxy marked `trustedOrigin`, are operator-only and answer 403 for a member. Deploying an uploaded build is deliberately TWO calls: upload into `builds/<id>/`, then set `{target:{kind:\"bundle\", dir:\"builds/<id>\"}}`.\n\n## Tabs\n\nEvery one is a real URL. A tab with its own section below carries a real job.\n\n| Tab | Path | Group | What it is for |\n|---|---|---|---|\n| `domains` | `/admin/hosted-domains` | - | Every domain connected to this account. |\n| `setup` | - | - | Connect a domain the operator owns, and prove they own it. |\n| `sites` | - | - | Which of your websites this domain serves - one row per hostname, and the one place a hostname ALIAS is set. |\n| `dns` | - | - | The records this domain needs, and whether they are live yet. |\n| `danger` | - | - | Disconnect this domain, or release the claim so somebody else can take it. |\n| `hosted-sites` | `/admin/hosted-sites` | - | Every website this operator has, with its status. |\n| `site-wizard` | `/admin/site-wizard` | - | The guided path from nothing to a first site. |\n| `manage` | - | - | The mode's own records - posts, links, files, images, releases. Only present for a mode that has them. |\n| `content` | - | - | The page this site serves at its root: its mode, and that mode's data payload. |\n| `pages` | - | - | Every page that is not the home page. |\n| `static` | - | - | Hand-written .html and .md files this site serves as real pages. |\n| `appearance` | - | - | Preset, colours, type, corners, logo - the whole look of the site. |\n| `seo` | - | - | Title, description, keywords and the social card image. |\n| `domains` | - | - | The subdomain this site answers on, and any custom domains attached to it. |\n| `analytics` | - | - | Who visited this site, and when. |\n| `target` | - | - | What this host actually serves: an Imperium page, a proxied app, an uploaded bundle, or a redirect. |\n| `files` | - | - | Upload a build for this site to serve. |\n| `connect` | - | - | Point this host at something the operator already runs, and test that it answers. |\n| `mcp` | - | - | Serve this site as a tool server that outside agents can call. |\n| `assets` | - | - | Records and assets scoped to this site. |\n| `code` | - | - | The whole site as one editable config - import and export it as a single file. |\n| `shield` | - | - | Per-site edge rules - who may reach this site at all, and how often. |\n| `danger` | - | - | Unpublish, rename and delete. |\n| `blueprints` | `/admin/blueprints` | - | Every blueprint this install offers and every one you own, and which of your sites is running each. A blueprint is a whole set-up — pages, portals, staff screens and the tables under them — so it sits ABOVE Page Builder, which edits one page of one. |\n| `page-builder` | `/admin/page-builder` | - | Compose a page visually, then bind it to a route as `page:<slug>`. |\n| `static-pages` | `/admin/static-pages` | - | Hand-written files any site can serve. |\n| `apps-registry` | `/admin/apps-registry` | - | The apps a site can point at. |\n| `deploy` | `/admin/deploy` | - | Push a build out. |\n| `client-routes` | `/admin/client-routes` | - | The PLATFORM app's own routes - a different scope from a site's pages. Do not cross them. |\n| `view-modes` | `/admin/view-modes` | - | Tenant-authored `x-` render modes, which is why the mode catalog must always be fetched. |\n| `page-views` | `/admin/page-views` | - | Traffic across every site. |\n| `global-settings` | `/admin/global-settings` | - | Defaults every site under this tenant inherits. |\n| `configs` | `/admin/configs` | - | The raw config documents behind every site. |\n\n## Tabs in focus\n\n### `setup` - Setup\n\nConnect a domain the operator owns, and prove they own it.\n\n**Read with:** `list_domains` | `check_domain`\n\n**Write with:** `connect_domain`\n\n**Trap:** Connecting writes no DNS anywhere - Leumas only READS it to prove ownership, so the person must publish the TXT record themselves. A domain serves NOTHING until it is `verified`; reporting a connect as done is the misleading half of a true sentence.\n\n### `sites` - Websites\n\nWhich of your websites this domain serves - one row per hostname, and the one place a hostname ALIAS is set.\n\n**Read with:** `list_hostnames` | `list_websites` | `get_alias_contract`\n\n**Write with:** `bind_hostname` | `unbind_hostname` | `POST /api/hosting/zones/:zoneId/bindings`\n\n1. A subdomain of a PROVED domain needs no DNS work at all - the wildcard record set up during verification already covers it. Say so; people expect to have to do something.\n2. `bind_hostname` points one address at one website. Several labels at once is a single act in the UI and several calls here.\n3. Prefer an ALIAS over a second website when the only difference is what it looks like or which kind of page it shows: pass `overrides` to `bind_hostname`.\n\n**Trap:** [critical] An alias is an ALLOWLIST of six presentation keys (mode, data, routes, appearance, skin, seo). Publish status, ownership, what the host serves and `baseApiUrl` belong to the website and are refused BY NAME - a binding must never be a way around the site lane. A `mode` override must bring its own `data`, or a payload shaped for one renderer reaches another that has never heard of it.\n\n### `hosted-sites` - My Websites\n\nEvery website this operator has, with its status.\n\nAddress: `/admin/hosted-sites`\n\n**Read with:** `list_my_sites` | `GET /api/hosting/sites`\n\n**Write with:** `start_new_website` | `open_site_editor`\n\n### `content` - Home\n\nThe page this site serves at its root: its mode, and that mode's data payload.\n\n**Read with:** `get_site_overview` | `list_modes` | `get_mode_schema` | `open_editor_tab`\n\n**Write with:** `set_site_content` | `save_site`\n\n1. `get_site_overview` FIRST - it returns the current mode, publish state, subdomain, SEO and the unsaved payload.\n2. Changing mode? `get_mode_schema(newMode)` for the field contract and a template before you write anything.\n3. `set_site_content` with only what changes.\n4. `save_site` - nothing above this line has persisted.\n\n**Trap:** This is the ONLY page that needs `save_site`. Also: asking for a template for a mode that does not exist silently returns the `default` template, so an empty-looking result is not proof the mode is real - check the catalog.\n\n### `pages` - Pages\n\nEvery page that is not the home page.\n\n**Read with:** `list_pages` | `GET /api/hosting/sites/:id/routes`\n\n**Write with:** `add_page` | `set_page_content` | `remove_page` | `POST /api/hosting/sites/:id/routes`\n\n1. `list_pages` for what exists and each page's id.\n2. `add_page` with an absolute `path` and a `componentKey`.\n3. `set_page_content` edits one; `remove_page` drops one.\n\n**Trap:** These write IMMEDIATELY - no `save_site`. A `componentKey` with an unrecognised prefix is re-read as a built-in and then refused with a message about the wrong thing, so check the prefix (`mode:`, `page:`, `dynamic:`, `library:`, `ssr:`, `static:`) before believing the error. And a non-public page is NEVER answered by the fast server-rendered plane - gating a page also hides it from crawlers and link previews, which is usually not what \"members only\" was meant to buy.\n\n### `appearance` - Appearance\n\nPreset, colours, type, corners, logo - the whole look of the site.\n\n**Read with:** `get_appearance` | `list_theme_presets` | `GET /api/hosting/theme-presets`\n\n**Write with:** `set_appearance` | `save_site`\n\n1. `get_appearance` - it returns the current values AND the contract, so there is no need to guess a field name.\n2. `list_theme_presets` if the user wants a whole look rather than one colour.\n3. `set_appearance` with only the keys that change.\n4. `save_site`.\n\n**Trap:** Colours are a grammar (hex, rgb, hsl, oklch, color-mix) and anything unparseable is dropped silently. Everything else is a closed list. Custom CSS answers 402 without the capability - that means \"not bought\", not \"not allowed\".\n\n### `seo` - SEO\n\nTitle, description, keywords and the social card image.\n\n**Read with:** `get_site_overview`\n\n**Write with:** `set_seo` | `save_site`\n\n**Trap:** Editor state - it does not persist until `save_site`.\n\n### `domains` - Domains\n\nThe subdomain this site answers on, and any custom domains attached to it.\n\n**Read with:** `GET /api/hosting/sites/:id/domains`\n\n**Write with:** `POST /api/hosting/sites/:id/domains` | `POST /api/hosting/sites/:id/domains/:host/verify`\n\n1. Set the hostnames, then read them back - the response carries the DNS records the user must add.\n2. Once the records are in place, run the verify call.\n\n**Trap:** A domain that is attached but not verified serves nothing and reports no error on the site itself - check here, not the front end. The site id IS the subdomain, so renaming one is a different, operator-only call.\n\n### `analytics` - Analytics\n\nWho visited this site, and when.\n\n**Read with:** `GET /api/hosting/sites/:id/telemetry` | `GET /api/hosting/activity`\n\n### `target` - Serves\n\nWhat this host actually serves: an Imperium page, a proxied app, an uploaded bundle, or a redirect.\n\n**Read with:** `get_target` | `GET /api/hosting/sites/:id`\n\n**Write with:** `set_target` | `PUT /api/hosting/sites/:id` | `POST /api/hosting/sites/:id/proxy-check`\n\n1. `get_target` FIRST - it returns the current target AND the exact fields every kind takes, so there is nothing to guess and nothing to remember.\n2. `set_target` with the WHOLE target including `kind`. The fields are per-kind, so a partial patch across kinds means nothing.\n\n**Trap:** [critical] `target` is NOT `mode`. `mode` decides how the home page renders; `target` decides whether the mode renderer is reached at all - only `kind: \"config\"` gets there. An `embed` mode iframes somebody else's site; a `proxy` target IS the site, on your origin, with your TLS and cookies. `static` and `server` are operator-only.\n\n### `files` - Files\n\nUpload a build for this site to serve.\n\n**Read with:** `GET /api/hosting/modes/:siteId/folder/tree` | `GET /api/hosting/modes/:siteId/folder/usage`\n\n**Write with:** `POST /api/hosting/modes/:siteId/folder/upload` | `POST /api/hosting/modes/:siteId/folder/dir`\n\n**Trap:** This prefix is the per-site STORAGE lane and has nothing to do with the mode catalog. Uploading a build does not serve it - that is a second call setting `target` to `{kind:\"bundle\", dir:\"builds/<id>\"}`.\n\n### `connect` - Your app\n\nPoint this host at something the operator already runs, and test that it answers.\n\n**Write with:** `POST /api/hosting/sites/:id/proxy-check`\n\n**Trap:** Only actionable for the modes that describe an external app. For everything else the answer lives on the Serves tab.\n\n### `mcp` - MCP\n\nServe this site as a tool server that outside agents can call.\n\n**Read with:** `GET /mcp/hosted` | `GET /api/hosting/sites/:id/grants`\n\n**Write with:** `POST /api/hosting/sites/:id/grants`\n\n**Trap:** A grant secret is returned ONCE, at creation. Anything beyond a private server answers 402 - a purchase, not a permission.\n\n### `code` - Code\n\nThe whole site as one editable config - import and export it as a single file.\n\n**Read with:** `GET /api/hosting/sites/:id/export`\n\n**Write with:** `POST /api/hosting/sites/:id/import`\n\n**Trap:** Editing here bypasses every field-level lane above, so the tiered write rules still apply and a refusal will be about a field, not the file.\n\n### `shield` - Shield\n\nPer-site edge rules - who may reach this site at all, and how often.\n\n**Read with:** `get_shield`\n\n**Write with:** `set_shield` | `PUT /api/hosting/sites/:id`\n\n**Trap:** [critical] An EMPTY crawler list means INHERIT the standard one, not \"nobody\". Writing `denyAgents: []` to be explicit removes the scraper protection the site already had. The deny list is also checked BEFORE the welcome list, so one broad pattern there de-indexes the site with nothing to read in a log.\n\n### `danger` - Danger\n\nUnpublish, rename and delete.\n\n**Write with:** `POST /api/hosting/sites/:id/unpublish` | `DELETE /api/hosting/sites/:id`\n\n**Trap:** Unpublish is ungated and reversible; delete is not, and it also sweeps every zone binding naming this site. Rename is operator-only because the id is the subdomain.\n\n### `blueprints` - Blueprints\n\nEvery blueprint this install offers and every one you own, and which of your sites is running each. A blueprint is a whole set-up — pages, portals, staff screens and the tables under them — so it sits ABOVE Page Builder, which edits one page of one.\n\nAddress: `/admin/blueprints`\n\n**Trap:** Three kinds of row and they are not interchangeable: an OPERATION is a pack, a STARTER is ours and cannot be edited (fork it and the copy is yours), and YOURS is a fork on one of your sites — the only editable kind. A pack’s parts are deliberately NOT listed one by one: a staff workspace installed on its own is a workspace with no public site and no portal.\n\n## Ways in\n\nDeclared cross-links. Every address is matched against the real route table by `check:huds`.\n\n| From | Does | Go to |\n|---|---|---|\n| `hosting/hosted-sites` | Start a website | `/admin/site-wizard` |\n\n## Tools an agent can call here\n\nThese are **live page capabilities**: they exist only while that surface is open, and they\ncall the page's own mutators, so a change made through one is a change the operator can see. A tool\nmissing from this list is not callable — reach for the API or an adapter instead.\n\n### `hosting:site-editor` — Site: …\n\nSource: `hosting/model/useSiteCapabilities.js`\n\nWhat the page tells an agent about itself:\n\n> Call get_site_overview first. Use get_mode_schema before changing a mode (or before setting a mode: page's props) so payloads fit the contract.\n>\n> TWO LANES, do not confuse them: the HOME page is config.mode + config.data — edit it with set_site_content and persist with save_site.\n>\n> Every OTHER page is a route: add_page / set_page_content / remove_page write immediately through the gated lane, so they need no save_site.\n>\n> Make surgical edits. Only pass publish:true when the user asks to go live. Report exactly what changed.\n\n| Tool | Args | Does |\n|---|---|---|\n| `get_site_overview` | — | Read this site: id, name, current mode, publish status, subdomain, SEO and the current (unsaved) data payload. Call FIRST before editing. |\n| `list_modes` | — | Every render mode this site can switch between (type, pageName, group). Call it rather than assuming - the catalog grows per release and a tenant can add its own. |\n| `get_mode_schema` | `mode` | A mode's data contract — the exact fields its payload supports — plus a default template. Call before set_site_content when changing modes or unsure of a field. |\n| `set_site_content` | `appName` `mode` `data` | Update this site's name, render mode and/or data payload IN THE EDITOR (the live preview updates; nothing persists until save_site). Pass only what changes. |\n| `set_seo` | `title` `description` `keywords` `image` | Update this site's SEO (title, description, keywords, social image URL). Editor state — persist with save_site. |\n| `list_theme_presets` | — | The named looks a site can start from (id, name, description, the appearance each applies). |\n| `get_appearance` | — | This site's current look PLUS the full appearance contract (every field and its allowed values). Call before set_appearance. |\n| `set_appearance` | `preset` `mode` `accent` `background` `surface` `text` `font` `radius` `density` `logo` | Change how the site LOOKS — preset, light/dark, colours, typeface, corners, spacing, logo. Editor state, so the preview updates immediately; persist with save_site. Pass only what changes; pass null for a key to clear it. |\n| `save_site` | `publish` | Persist the current editor state (the same save the buttons use — validated server-side). publish:true also pushes the site live. |\n| `open_editor_tab` | `tab` | Switch the editor to a tab: manage (the mode’s own records — posts, links, files — when the mode has them), content (the home page), pages, appearance, seo, domains, connect, target, files, mcp, shield, analytics, assets, code, danger. |\n| `list_pages` | — | This site's pages: address, title, what each shows (componentKey), who can open it, whether it is in the menu. Call before adding or editing one. |\n| `add_page` | `path` `componentKey` `props` `label` `guard` `navVisible` | Add a page. componentKey is usually \"mode:<type>\" (any render mode as a page) — then `props` is that mode's data payload, so call get_mode_schema first. Also valid: \"page:<slug>\" (a Page Builder page) or \"library:<path>\". |\n| `set_page_content` | `id` `path` `componentKey` `props` `label` `guard` `navVisible` `enabled` | Update one page by its id (from list_pages). Pass only what changes — `props` replaces that page's data payload. |\n| `remove_page` | `id` | Delete a page by its id (from list_pages). The home page is config.mode/data, not a page — use set_site_content for that. |\n| `get_target` | — | What this host SERVES, and the exact fields each kind takes. `kind` decides everything else: config (the pages you author here) \\| proxy (an app already running elsewhere) \\| server (a local app Imperium starts) \\| static \\| bundle (a build uploaded to this site) \\| redirect. Call before set_target. |\n| `set_target` | `kind` `origin` `dir` `port` `to` | Change what this host serves. Pass the whole target including `kind` — the fields are per-kind, so a partial patch across kinds is meaningless. Writes immediately; `static` and `server` are operator-only and will be refused for a member. |\n| `set_page_gate` | `id` `guard` | Who can open one page: public (anyone) \\| auth (signed in) \\| membership (an active membership). Takes a page id from list_pages. NOTE a non-public page is only served after the site's app boots, so a crawler or a link preview sees nothing on it. |\n| `get_shield` | — | This site's edge rules — rate limit, welcome and refusal crawler lists, honeypot paths — plus the full contract (every field and what it means). Call before set_shield. |\n| `set_shield` | `enabled` `maxRpm` `allowAgents` `denyAgents` `blockPaths` | Change the edge rules. Pass only what changes. Everything is inert while `enabled` is false. |\n\n**2 more tool(s) on this scope are named at runtime** (a computed key, or built\nby a helper), so they cannot be listed here. Call `listTools` on the live page to see them.\n\n### `hosting:websites-home` — My Websites\n\nSource: `hosting/model/useSiteCapabilities.js`\n\nWhat the page tells an agent about itself:\n\n> list_my_sites shows what exists; open a site before trying to edit it — the editor page grants the editing tools.\n\n| Tool | Args | Does |\n|---|---|---|\n| `list_my_sites` | — | The signed-in user's Imperium websites: id, name, mode, publish status, subdomain. |\n| `open_site_editor` | `id` | Open a site in the Site Editor (where content/SEO/publish tools become available). |\n| `start_new_website` | — | Open the New Website wizard (template gallery → subdomain claim → content → publish). |\n\n### `unnamed scope`\n\nSource: `hosting/model/useZoneCapabilities.js`\n\n\n**1 more tool(s) on this scope are named at runtime** (a computed key, or built\nby a helper), so they cannot be listed here. Call `listTools` on the live page to see them.\n\n### `hosting:domains` — Connected domains\n\nSource: `hosting/model/useZoneCapabilities.js`\n\nWhat the page tells an agent about itself:\n\n> Connecting a domain writes no DNS anywhere — Leumas only READS it to prove ownership. The person must publish the TXT record themselves, then check_domain confirms it.\n>\n> A domain serves nothing until it is `verified`. Say that plainly rather than reporting a connect as done.\n>\n> Open a domain to bind hostnames under it; the binding tools live on that page.\n\n| Tool | Args | Does |\n|---|---|---|\n| `list_domains` | — | The domains this account has connected: host, whether ownership is proved, and how many hostnames are bound under each. A domain must be `verified` before anything on it serves. |\n| `connect_domain` | `host` | Claim a domain this account owns. It starts `pending` and serves nothing until the TXT record in the result is published and check_domain passes. Idempotent — connecting one already connected returns it. |\n| `check_domain` | `id` | Ask DNS whether the ownership record is published yet, and flip the domain to verified if it is. Safe to call repeatedly; DNS can take minutes to hours to propagate. |\n| `open_domain` | `id` | Open one domain’s setup checklist, where the DNS records and the hostname map live. |\n\n### `hosting:domain` — Domain: …\n\nSource: `hosting/model/useZoneCapabilities.js`\n\nWhat the page tells an agent about itself:\n\n> Adding a subdomain of a PROVED domain needs no DNS work at all — that is what the wildcard record set up during verification is for. Say so; people expect to have to do something.\n>\n> One website can answer on several hostnames. Prefer an alias over a second website when the difference is only what it looks like or which kind of page it shows.\n>\n> Removing a hostname does not delete the website behind it.\n\n| Tool | Args | Does |\n|---|---|---|\n| `list_hostnames` | — | Every hostname on this domain and what it serves — including whether an address is ALIASED (serving the bound website differently from its other addresses). |\n| `list_websites` | — | The websites that can be put on this domain (the signed-in account’s own). |\n| `bind_hostname` | `host` `siteId` `overrides` | Point a hostname on this domain at one of the account's websites. A subdomain of a proved domain needs NO DNS work — the wildcard record already covers it. Pass the full hostname (blog.example.com), or the domain itself for the apex. |\n| `unbind_hostname` | `host` | Stop serving a hostname. The website itself is untouched — this only removes the address. |\n| `get_alias_contract` | — | The exact fields a hostname alias may carry, and what each does. Call before passing `overrides` to bind_hostname. |\n\n## API\n\nMounted prefixes that serve this domain: `/api/hosting` · `/api/hosting/modes`\n\nThe full route table is `leumas-capabilities/reference/api-routes.md`.\n\n## Traps\n\nThe authed lane is `/api/hosting`; the site STORAGE lane is `/api/hosting/modes/:siteId/...`; the mode CATALOG is `GET /index/modes`. The middle one is not the last one. Read the status code: 402 means the operator may do this and has not bought it (custom CSS, MCP visibility, quota). 403 means operator-only (static/server targets, trustedOrigin, rename, theme/header/footer). Sub-tab paths under a site are declared WITHOUT the site segment - `/admin/hosting/sites/domains` is the SHAPE; the running app supplies the id. A site id is its subdomain, and a deleted subdomain becomes claimable again.\n\n",
  "source": {
    "path": ".claude/skills/leumas-studio/reference/domains/hosting.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 27013,
    "hash": "b2c659f68e60793aac22f517b85fbecb8f7f07b7"
  },
  "urls": {
    "html": "/p/skills/leumas-studio/domains/hosting",
    "json": "/docs/skills/leumas-studio/domains/hosting.json",
    "md": "/docs/skills/leumas-studio/domains/hosting.md"
  },
  "links": {
    "composes": [],
    "usedBy": [],
    "product": [],
    "howTo": [],
    "skills": []
  }
}
