Files
website/server/routes.manifest.json
wtclaude 291c30f6ff
All checks were successful
PR Checks / client-build (pull_request) Successful in 26s
PR Checks / server-tests (pull_request) Successful in 1m33s
PR Checks / bot-install (pull_request) Successful in 8m45s
feat(modules): publish the installed-module list at /api/v1/public/modules
Phase 2, PR 6 of docs/website/MODULE_SYSTEM.md 2.7 — the first module-system
URL a client can see. The SPA and the Android app feature-detect against the
capabilities a module declares; the shape is settled in MODULE_API.md 2.9.

Four decisions, and what is absent from the payload is most of the design:

* started modules only. A module that is disabled or failed to load is
  ABSENT, exactly as 4.4 already leaves its routes and its nav absent, so a
  client renders a site without that capability rather than advertising one
  that 503s.
* no state, failure_stage or failure_reason. Where a module broke belongs to
  the admin Modules screen, and the reason is an exception string from inside
  core — not anonymous-visitor business.
* no client chunk URL. htmlShell injects a script tag per started module
  (3.1.3), so the browser is handed the tag rather than a URL to fetch. This
  endpoint feature-detects; it does not load. MODULE_SYSTEM 2.6 step 4 is
  amended to match (API 6.7).
* no siteMode gate and no database — the same class as /public/status and
  /public/version, so a client can still feature-detect during maintenance.

It is a capability router of its own rather than a fifth singleton in
site.router.js, and that is load-bearing: the loader's prefix-collision probe
reads the live tier stack and skips root-mounted layers, because a use('/', ...)
matches every path. A route inside the root-mounted site router would be
invisible to it — mounting use('/modules', ...) is what makes "no module may
claim /modules" a rule the loader enforces.

910 tests pass (+9, every one on the boundary — what must NOT appear).
routes.manifest.json gains exactly the one route and routes.guards.json records
it with an empty gates list, which is itself the assertion that it is ungated.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-10 22:03:22 -05:00

928 lines
19 KiB
JSON

{
"$comment": "Generated route inventory - the authoritative freeze of the URL surface. Regenerate with `npm run routes:manifest` in website/server; a domain-split PR must produce a zero-line diff here.",
"public": [
{
"method": "GET",
"path": "/.well-known/assetlinks.json"
},
{
"method": "POST",
"path": "/api/csp-report"
},
{
"method": "GET",
"path": "/api/docs.json"
},
{
"method": "GET",
"path": "/api/health"
},
{
"method": "GET",
"path": "/api/v1/admin/account"
},
{
"method": "GET",
"path": "/api/v1/admin/account/identities"
},
{
"method": "DELETE",
"path": "/api/v1/admin/account/identities/:provider"
},
{
"method": "POST",
"path": "/api/v1/admin/account/totp/disable"
},
{
"method": "POST",
"path": "/api/v1/admin/account/totp/enable"
},
{
"method": "POST",
"path": "/api/v1/admin/account/totp/setup"
},
{
"method": "GET",
"path": "/api/v1/admin/activity"
},
{
"method": "GET",
"path": "/api/v1/admin/auth/providers"
},
{
"method": "POST",
"path": "/api/v1/admin/auth/providers"
},
{
"method": "DELETE",
"path": "/api/v1/admin/auth/providers/:id"
},
{
"method": "PUT",
"path": "/api/v1/admin/auth/providers/:id"
},
{
"method": "GET",
"path": "/api/v1/admin/bot-activity"
},
{
"method": "POST",
"path": "/api/v1/admin/bot-activity/unban"
},
{
"method": "GET",
"path": "/api/v1/admin/dashboard"
},
{
"method": "GET",
"path": "/api/v1/admin/discord-bot/config"
},
{
"method": "PUT",
"path": "/api/v1/admin/discord-bot/config"
},
{
"method": "GET",
"path": "/api/v1/admin/email/config"
},
{
"method": "PUT",
"path": "/api/v1/admin/email/config"
},
{
"method": "GET",
"path": "/api/v1/admin/email/connect/callback"
},
{
"method": "GET",
"path": "/api/v1/admin/email/connect/start"
},
{
"method": "POST",
"path": "/api/v1/admin/email/disconnect"
},
{
"method": "POST",
"path": "/api/v1/admin/email/test"
},
{
"method": "GET",
"path": "/api/v1/admin/invites"
},
{
"method": "POST",
"path": "/api/v1/admin/invites"
},
{
"method": "DELETE",
"path": "/api/v1/admin/invites/:id"
},
{
"method": "GET",
"path": "/api/v1/admin/moderation/appeals"
},
{
"method": "GET",
"path": "/api/v1/admin/moderation/appeals/:id"
},
{
"method": "POST",
"path": "/api/v1/admin/moderation/appeals/:id/claim"
},
{
"method": "POST",
"path": "/api/v1/admin/moderation/appeals/:id/resolve"
},
{
"method": "GET",
"path": "/api/v1/admin/moderation/filter-hits"
},
{
"method": "GET",
"path": "/api/v1/admin/moderation/members"
},
{
"method": "GET",
"path": "/api/v1/admin/moderation/recent"
},
{
"method": "GET",
"path": "/api/v1/admin/moderation/search"
},
{
"method": "GET",
"path": "/api/v1/admin/moderation/spam-hits"
},
{
"method": "GET",
"path": "/api/v1/admin/moderation/stats/summary"
},
{
"method": "GET",
"path": "/api/v1/admin/moderation/user/:discordId"
},
{
"method": "GET",
"path": "/api/v1/admin/moderation/user/:discordId/actions"
},
{
"method": "GET",
"path": "/api/v1/admin/moderation/user/:discordId/appeals"
},
{
"method": "GET",
"path": "/api/v1/admin/moderation/user/:discordId/notes"
},
{
"method": "POST",
"path": "/api/v1/admin/moderation/user/:discordId/notes"
},
{
"method": "GET",
"path": "/api/v1/admin/pages"
},
{
"method": "POST",
"path": "/api/v1/admin/pages"
},
{
"method": "DELETE",
"path": "/api/v1/admin/pages/:id"
},
{
"method": "GET",
"path": "/api/v1/admin/pages/:id"
},
{
"method": "PATCH",
"path": "/api/v1/admin/pages/:id"
},
{
"method": "POST",
"path": "/api/v1/admin/pages/:id/preview"
},
{
"method": "POST",
"path": "/api/v1/admin/pages/:id/unprotect"
},
{
"method": "GET",
"path": "/api/v1/admin/posts"
},
{
"method": "POST",
"path": "/api/v1/admin/posts"
},
{
"method": "DELETE",
"path": "/api/v1/admin/posts/:id"
},
{
"method": "GET",
"path": "/api/v1/admin/posts/:id"
},
{
"method": "PUT",
"path": "/api/v1/admin/posts/:id"
},
{
"method": "GET",
"path": "/api/v1/admin/posts/:id/announce"
},
{
"method": "POST",
"path": "/api/v1/admin/posts/:id/announce/retry"
},
{
"method": "PATCH",
"path": "/api/v1/admin/posts/:id/publish"
},
{
"method": "POST",
"path": "/api/v1/admin/posts/upload"
},
{
"method": "GET",
"path": "/api/v1/admin/settings"
},
{
"method": "PUT",
"path": "/api/v1/admin/settings"
},
{
"method": "DELETE",
"path": "/api/v1/admin/settings/:key"
},
{
"method": "POST",
"path": "/api/v1/admin/settings/brand-asset/:slot"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/account"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/accounts"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/atlas"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/atlas/approve"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/atlas/import"
},
{
"method": "PUT",
"path": "/api/v1/admin/shard/atlas/path"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/atlas/reject"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/audit"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/ban"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/broadcast"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/char/:serial"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/clilocs"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/clilocs/import"
},
{
"method": "PUT",
"path": "/api/v1/admin/shard/clilocs/path"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/houses"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/kick"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/link"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/pages"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/pages/:id/close"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/pages/:id/respond"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/roster/:account"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/sales"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/unban"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/vendors/:account"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/visibility"
},
{
"method": "PUT",
"path": "/api/v1/admin/shard/visibility"
},
{
"method": "PUT",
"path": "/api/v1/admin/site-mode"
},
{
"method": "GET",
"path": "/api/v1/admin/uo-link/config"
},
{
"method": "PUT",
"path": "/api/v1/admin/uo-link/config"
},
{
"method": "GET",
"path": "/api/v1/admin/uo-link/stream"
},
{
"method": "POST",
"path": "/api/v1/admin/uo-link/towncrier"
},
{
"method": "DELETE",
"path": "/api/v1/admin/uo-link/towncrier/:id"
},
{
"method": "POST",
"path": "/api/v1/admin/uploads"
},
{
"method": "GET",
"path": "/api/v1/admin/users"
},
{
"method": "POST",
"path": "/api/v1/admin/users"
},
{
"method": "DELETE",
"path": "/api/v1/admin/users/:id"
},
{
"method": "GET",
"path": "/api/v1/admin/users/:id"
},
{
"method": "PUT",
"path": "/api/v1/admin/users/:id"
},
{
"method": "POST",
"path": "/api/v1/admin/users/:id/mfa/reset"
},
{
"method": "GET",
"path": "/api/v1/admin/users/:id/shard/accounts"
},
{
"method": "GET",
"path": "/api/v1/admin/users/:id/shard/houses"
},
{
"method": "DELETE",
"path": "/api/v1/admin/users/:id/shard/link/:account"
},
{
"method": "GET",
"path": "/api/v1/admin/users/:id/shard/online"
},
{
"method": "GET",
"path": "/api/v1/admin/users/:id/shard/sales"
},
{
"method": "GET",
"path": "/api/v1/admin/users/:id/shard/standing"
},
{
"method": "DELETE",
"path": "/api/v1/admin/users/:id/trusted-devices"
},
{
"method": "GET",
"path": "/api/v1/admin/users/:id/trusted-devices"
},
{
"method": "DELETE",
"path": "/api/v1/admin/users/:id/trusted-devices/:deviceId"
},
{
"method": "GET",
"path": "/api/v1/admin/wiki"
},
{
"method": "POST",
"path": "/api/v1/admin/wiki"
},
{
"method": "DELETE",
"path": "/api/v1/admin/wiki/:slug"
},
{
"method": "GET",
"path": "/api/v1/admin/wiki/:slug"
},
{
"method": "PUT",
"path": "/api/v1/admin/wiki/:slug"
},
{
"method": "PATCH",
"path": "/api/v1/admin/wiki/:slug/publish"
},
{
"method": "GET",
"path": "/api/v1/admin/wiki/:slug/revisions"
},
{
"method": "GET",
"path": "/api/v1/admin/wiki/:slug/revisions/:id"
},
{
"method": "POST",
"path": "/api/v1/admin/wiki/:slug/revisions/:id/restore"
},
{
"method": "GET",
"path": "/api/v1/admin/wiki/categories"
},
{
"method": "POST",
"path": "/api/v1/admin/wiki/categories"
},
{
"method": "DELETE",
"path": "/api/v1/admin/wiki/categories/:id"
},
{
"method": "PUT",
"path": "/api/v1/admin/wiki/categories/:id"
},
{
"method": "GET",
"path": "/api/v1/admin/wiki/tags"
},
{
"method": "GET",
"path": "/api/v1/auth/invite/:token"
},
{
"method": "POST",
"path": "/api/v1/auth/invite/:token/accept"
},
{
"method": "POST",
"path": "/api/v1/auth/login"
},
{
"method": "POST",
"path": "/api/v1/auth/login/totp"
},
{
"method": "POST",
"path": "/api/v1/auth/logout"
},
{
"method": "GET",
"path": "/api/v1/auth/me"
},
{
"method": "GET",
"path": "/api/v1/auth/me/account"
},
{
"method": "GET",
"path": "/api/v1/auth/me/account/identities"
},
{
"method": "DELETE",
"path": "/api/v1/auth/me/account/identities/:provider"
},
{
"method": "PATCH",
"path": "/api/v1/auth/me/account/password"
},
{
"method": "POST",
"path": "/api/v1/auth/me/account/recovery-codes/generate"
},
{
"method": "GET",
"path": "/api/v1/auth/me/account/recovery-codes/status"
},
{
"method": "POST",
"path": "/api/v1/auth/me/account/totp/disable"
},
{
"method": "POST",
"path": "/api/v1/auth/me/account/totp/enable"
},
{
"method": "POST",
"path": "/api/v1/auth/me/account/totp/setup"
},
{
"method": "PATCH",
"path": "/api/v1/auth/me/account/username"
},
{
"method": "GET",
"path": "/api/v1/auth/me/devices"
},
{
"method": "POST",
"path": "/api/v1/auth/me/devices"
},
{
"method": "DELETE",
"path": "/api/v1/auth/me/devices/:id"
},
{
"method": "GET",
"path": "/api/v1/auth/me/notifications/streams"
},
{
"method": "GET",
"path": "/api/v1/auth/me/notifications/subscriptions"
},
{
"method": "PUT",
"path": "/api/v1/auth/me/notifications/subscriptions"
},
{
"method": "GET",
"path": "/api/v1/auth/me/sessions"
},
{
"method": "DELETE",
"path": "/api/v1/auth/me/sessions/:id"
},
{
"method": "DELETE",
"path": "/api/v1/auth/me/trusted-devices"
},
{
"method": "GET",
"path": "/api/v1/auth/me/trusted-devices"
},
{
"method": "POST",
"path": "/api/v1/auth/me/trusted-devices"
},
{
"method": "DELETE",
"path": "/api/v1/auth/me/trusted-devices/:id"
},
{
"method": "POST",
"path": "/api/v1/auth/mobile/login"
},
{
"method": "POST",
"path": "/api/v1/auth/mobile/logout"
},
{
"method": "POST",
"path": "/api/v1/auth/mobile/refresh"
},
{
"method": "POST",
"path": "/api/v1/auth/mobile/sso/exchange"
},
{
"method": "GET",
"path": "/api/v1/auth/mobile/sso/start"
},
{
"method": "POST",
"path": "/api/v1/auth/password/forgot"
},
{
"method": "GET",
"path": "/api/v1/auth/password/reset/:token"
},
{
"method": "POST",
"path": "/api/v1/auth/password/reset/:token"
},
{
"method": "GET",
"path": "/api/v1/auth/providers"
},
{
"method": "POST",
"path": "/api/v1/auth/register"
},
{
"method": "GET",
"path": "/api/v1/auth/sso/:provider/callback"
},
{
"method": "GET",
"path": "/api/v1/auth/sso/:provider/link"
},
{
"method": "GET",
"path": "/api/v1/auth/sso/:provider/start"
},
{
"method": "POST",
"path": "/api/v1/auth/sso/totp"
},
{
"method": "GET",
"path": "/api/v1/player/account"
},
{
"method": "GET",
"path": "/api/v1/player/account/identities"
},
{
"method": "DELETE",
"path": "/api/v1/player/account/identities/:provider"
},
{
"method": "PATCH",
"path": "/api/v1/player/account/password"
},
{
"method": "POST",
"path": "/api/v1/player/account/totp/disable"
},
{
"method": "POST",
"path": "/api/v1/player/account/totp/enable"
},
{
"method": "POST",
"path": "/api/v1/player/account/totp/setup"
},
{
"method": "PATCH",
"path": "/api/v1/player/account/username"
},
{
"method": "GET",
"path": "/api/v1/player/appeals"
},
{
"method": "POST",
"path": "/api/v1/player/appeals"
},
{
"method": "POST",
"path": "/api/v1/player/appeals/:id/withdraw"
},
{
"method": "GET",
"path": "/api/v1/player/appeals/eligible"
},
{
"method": "POST",
"path": "/api/v1/player/shard/account"
},
{
"method": "GET",
"path": "/api/v1/player/shard/accounts"
},
{
"method": "GET",
"path": "/api/v1/player/shard/char/:serial"
},
{
"method": "GET",
"path": "/api/v1/player/shard/houses"
},
{
"method": "POST",
"path": "/api/v1/player/shard/link"
},
{
"method": "GET",
"path": "/api/v1/player/shard/roster/:account"
},
{
"method": "GET",
"path": "/api/v1/player/shard/sales"
},
{
"method": "GET",
"path": "/api/v1/player/shard/vendors/:account"
},
{
"method": "GET",
"path": "/api/v1/public/atlas/champions"
},
{
"method": "GET",
"path": "/api/v1/public/atlas/creatures"
},
{
"method": "GET",
"path": "/api/v1/public/atlas/creatures/:slug"
},
{
"method": "GET",
"path": "/api/v1/public/atlas/landmarks"
},
{
"method": "GET",
"path": "/api/v1/public/atlas/meta"
},
{
"method": "GET",
"path": "/api/v1/public/atlas/regions"
},
{
"method": "POST",
"path": "/api/v1/public/contact"
},
{
"method": "GET",
"path": "/api/v1/public/modules"
},
{
"method": "GET",
"path": "/api/v1/public/pages/:id/preview/:token"
},
{
"method": "GET",
"path": "/api/v1/public/pages/:slug"
},
{
"method": "GET",
"path": "/api/v1/public/posts/:category"
},
{
"method": "GET",
"path": "/api/v1/public/posts/:category/:idOrSlug"
},
{
"method": "GET",
"path": "/api/v1/public/settings"
},
{
"method": "GET",
"path": "/api/v1/public/shard/champs"
},
{
"method": "GET",
"path": "/api/v1/public/shard/economy"
},
{
"method": "GET",
"path": "/api/v1/public/shard/features"
},
{
"method": "GET",
"path": "/api/v1/public/shard/feed"
},
{
"method": "GET",
"path": "/api/v1/public/shard/governors"
},
{
"method": "GET",
"path": "/api/v1/public/shard/governors/:city/history"
},
{
"method": "GET",
"path": "/api/v1/public/shard/guilds"
},
{
"method": "GET",
"path": "/api/v1/public/shard/houses"
},
{
"method": "GET",
"path": "/api/v1/public/shard/idoc"
},
{
"method": "GET",
"path": "/api/v1/public/shard/market"
},
{
"method": "GET",
"path": "/api/v1/public/shard/market/meta"
},
{
"method": "GET",
"path": "/api/v1/public/shard/market/vendors/:serial"
},
{
"method": "GET",
"path": "/api/v1/public/shard/online"
},
{
"method": "GET",
"path": "/api/v1/public/shard/points"
},
{
"method": "GET",
"path": "/api/v1/public/shard/points/:system"
},
{
"method": "GET",
"path": "/api/v1/public/shard/presence"
},
{
"method": "GET",
"path": "/api/v1/public/shard/ruleset"
},
{
"method": "GET",
"path": "/api/v1/public/shard/status"
},
{
"method": "GET",
"path": "/api/v1/public/shard/stream"
},
{
"method": "GET",
"path": "/api/v1/public/status"
},
{
"method": "GET",
"path": "/api/v1/public/version"
},
{
"method": "GET",
"path": "/api/v1/public/wiki"
},
{
"method": "GET",
"path": "/api/v1/public/wiki/:slug"
},
{
"method": "GET",
"path": "/api/v1/public/wiki/categories"
},
{
"method": "GET",
"path": "/api/v1/public/wiki/tags"
},
{
"method": "GET",
"path": "/api/v1/settings/nav"
},
{
"method": "GET",
"path": "/api/v1/settings/theme/options"
}
],
"internal": [
{
"method": "GET",
"path": "/health"
},
{
"method": "GET",
"path": "/internal/bot-config"
}
]
}