# Server Directory > **Area:** server-directory > **Status:** Active > **Last updated:** 2026-07-05 ## Overview Server directory is the cross-context contract for **which signaling servers exist**, how clients health-check and route to them, and how public/private chat-servers are created, joined, updated, moderated, and discovered over REST. It spans the signaling **server** (`server/src/routes/servers.ts`, CQRS handlers) and the product **client** (`server-directory` domain + `ServerDirectoryFacade`). Curated browse lists (featured/trending) are documented separately in [server-discovery.md](server-discovery.md). WebSocket membership (`join_server`, presence) is in [signaling.md](signaling.md). ## Responsibilities - Server: persist public server records, memberships, channels, roles, bans, invites, join requests; expose REST CRUD and access checks. - Client: maintain configured endpoint list, health/compatibility probes, canonical endpoint dedup by `serverInstanceId`, room `sourceId`/`sourceUrl` affinity, and HTTP orchestration for all server operations. - It does NOT own: P2P chat transport, voice WebRTC, or local Electron room/message persistence (except mirroring server metadata into local DB after join). ## Key concepts - **ServerEndpoint:** configured signaling base URL with health status, latency, and version compatibility. - **ServerInfo:** public server card shape returned by search/discovery/GET — includes `sourceId`, `sourceName`, `sourceUrl` filled by the client API layer. - **Room signal affinity:** each saved room records which endpoint registered it; reconnect prefers that URL before fallback endpoints. - **serverInstanceId:** stable id from `GET /api/health` used to collapse alias URLs to one canonical endpoint. ## Public REST (no bearer) | Method | Path | Purpose | |--------|------|---------| | GET | `/api/health` | Liveness, `serverVersion`, `serverInstanceId`, optional `serverTag` | | GET | `/api/servers` | Free-text search / public listing (`q`, `limit`) | | GET | `/api/servers/featured` | Curated popular list — [server-discovery.md](server-discovery.md) | | GET | `/api/servers/trending` | Curated active list — [server-discovery.md](server-discovery.md) | | GET | `/api/servers/:id` | Single server metadata | ## Protected REST (bearer required) All mutations derive the actor from the session token; body user ids are not trusted. | Method | Path | Purpose | |--------|------|---------| | POST | `/api/servers` | Register a new public server | | PUT | `/api/servers/:id` | Update name, description, channels, icon metadata, access settings | | DELETE | `/api/servers/:id` | Unregister server (owner) | | POST | `/api/servers/:id/join` | Join or request access (password, invite, public) | | POST | `/api/servers/:id/leave` | Leave membership | | POST | `/api/servers/:id/heartbeat` | Refresh `lastSeen` for trending ranking | | POST | `/api/servers/:id/invites` | Create invite link — [invites-join-requests.md](invites-join-requests.md) | | GET | `/api/servers/:id/requests` | List pending join requests (moderators) | | POST | `/api/servers/:id/moderation/kick` | Remove member | | POST | `/api/servers/:id/moderation/ban` | Ban member (optional expiry) | | POST | `/api/servers/:id/moderation/unban` | Lift ban | Join-request approval: `PUT /api/requests/:id` — [invites-join-requests.md](invites-join-requests.md). Plugin metadata under `/api/servers/:serverId/plugins` — [plugins.md](plugins.md). ## Client endpoint lifecycle 1. Load endpoints from `localStorage` (`metoyou_server_endpoints`); reconcile with environment defaults. 2. `testAllServers()` probes `GET /api/health` (5 s timeout); on failure falls back to `GET /api/servers`. 3. Mark incompatible when `serverVersion` fails semantic compatibility check. 4. `resolveCanonicalEndpoint()` collapses aliases sharing the same `serverInstanceId`. 5. Cold-start room reconnect waits for the initial health sweep before opening WebSockets. ## Multi-endpoint behavior | Operation | Fan-out | |-----------|---------| | Search (`searchServers` with `searchAllServers`) | All online endpoints, dedupe by server id | | Discovery (featured/trending) | All online endpoints + 404→public list fallback | | Room CRUD/join | Authoritative room `sourceUrl` first; temporary fallback to other compatible endpoints on outage | Only `status === 'incompatible'` stops fallback with an update-required message. Network errors and Cloudflare 521/522 must continue to the next endpoint. ## Server-owned channel metadata `PUT /api/servers/:id` persists the server's `channels` array (text + voice). The client round-trips channel create/rename/delete through this API — local-only channel state is not authoritative. Server-side normalisation deduplicates names within each channel type. ## WebSocket complement After REST join, the client sends `join_server` on the room's signaling URL. Presence (`server_users`, `user_joined`, `user_left`) is room-scoped on the WebSocket — see [signaling.md](signaling.md). ## Related - [server-discovery.md](server-discovery.md) — featured/trending ranking and browse UI - [authentication.md](authentication.md) — bearer tokens for mutations - [invites-join-requests.md](invites-join-requests.md) — invite links and approval workflow - [signal-server-tag.md](signal-server-tag.md) — `serverTag` on health + profile cards - Product-client domain README: [`toju-app/src/app/domains/server-directory/README.md`](../../toju-app/src/app/domains/server-directory/README.md) ## Changelog | Date | Change | |------|--------| | 2026-07-05 | Initial cross-context server-directory REST contract |