HTTP endpoints
All ShellWatch endpoints live on the same Fastify app on the same port (default 3000). The same listener serves the SvelteKit SPA, the REST API, the WebSocket, the MCP transport, the SSH agent proxy, the WebAuthn routes, and the OAuth glue (mediated client registration, discovery metadata, and the passkey login/consent pages for Ory Hydra).
The OAuth2 protocol endpoints themselves — /oauth2/auth, /oauth2/token, /oauth2/revoke, /oauth2/sessions/logout, /.well-known/jwks.json — are served by Hydra’s public port at the issuer URL (hydra.publicUrl), not by ShellWatch. In a single-domain deployment your reverse proxy routes those paths to Hydra; see Self-hosting → Reverse proxy.
| Path | Type | Purpose |
|---|---|---|
/ | static | Web UI — terminal view (SvelteKit SPA) |
/session/:id | static | Web UI — specific session terminal |
/observer | static | Web UI — multi-session grid |
/sign/:id | static | Web UI — sign-request approval |
/register | static | Web UI — WebAuthn registration (gated by selfRegistrationEnabled) |
/passkey-invite/:token | static | Token-gated cross-device passkey enrollment |
/settings/* | static | Web UI — general, endpoints, keys, passkeys, sessions, notifications |
/audit/* | static | Web UI — /audit/sessions, /audit/signings |
/admin/* | static | Web UI — /admin/accounts, /admin/general (admins only) |
/auth/callback | static (SPA) | The web UI’s OAuth redirect URI — handled client-side, not a server route |
/api/* | REST | Endpoints, sessions, keys, passkeys, accounts, actions, audit, push |
/api/version | REST | Build SHA + ref. Public; same payload also embedded into the SPA shell. |
/api/webauthn/* | REST | WebAuthn ceremonies + passkey-management: registration, login, credential add/revoke/confirm, step-up, invite. The auth posture varies per route — see Auth at a glance below for the breakdown. |
/api/webauthn/stepup/options | REST | Issue a step-up assertion challenge (/api/webauthn/stepup/verify accepts the response). |
/api/webauthn/invite | REST | Issue a cross-device passkey-enrollment token (POST); inspect the live token (GET). Step-up gated. |
/api/webauthn/credentials/:id/confirm | REST | Confirm a credential registered via the invite flow. Step-up gated. |
/api/passkey-invite/:token | REST | Validate an invite token (server-side). |
/api/passkey-invite/register/options | REST | Token-gated registration options for the invite flow. |
/api/passkey-invite/register | REST | Token-gated registration verification for the invite flow. |
/api/actions/:id | REST | GET action context, POST .../resolve, POST .../deny |
/api/sessions/:id/tail | REST | Snapshot of a session’s recent output (used by the agent-forwarding sign page) |
/api/audit/sessions | REST | Keyset-paginated session-lifecycle audit log (account-scoped) |
/api/audit/signings | REST | Keyset-paginated signing-request audit log (account-scoped) |
/api/auth/sessions | REST | GET lists the account’s authorized OAuth clients (web UI, MCP, agent) with scopes + last-authorization time. Backs Settings → Sessions. |
/api/auth/sessions/:clientId | REST | DELETE revokes one client’s tokens/grant (step-up gated). |
/api/auth/sessions/revoke-all | REST | POST revokes every consent grant + login session for the account — signs out everywhere (step-up gated). |
/ws | WebSocket | Terminal I/O + sessions:changed + sign:request / sign:resolved |
/mcp | MCP (streamable HTTP) | Model Context Protocol — Bearer auth, mcp scope |
/agent-proxy | WebSocket | SSH agent proxy — Bearer auth, agent scope |
/api/hydra/register | OAuth (DCR) | Mediated Dynamic Client Registration — validates redirect URI + scope against local policy (hydra.dcr), then provisions a real per-client client_id in Hydra. Lives under /api/hydra/* so a reverse proxy routes it to ShellWatch with no special-casing (Hydra’s own DCR stays disabled). |
/api/hydra/login | OAuth (provider) | Server-rendered passkey login page — Hydra redirects the browser here during the authorization flow. (/options + /verify sub-routes run the WebAuthn ceremony.) This replaces the old SPA /login screen. |
/api/hydra/consent | OAuth (provider) | Consent page — scope approval after login. Auto-accepted for the first-party SPA client; a fresh login can approve a third-party client with no extra passkey tap (see Accounts). |
/api/hydra/logout | OAuth (provider) | RP-initiated logout callback. Requires an id_token_hint — an unhinted (CSRF) logout is rejected rather than silently terminating the session. |
/api/hydra/error | OAuth (provider) | OAuth error landing page. |
/.well-known/oauth-protected-resource | OAuth | RFC 9728 protected-resource metadata (also per-resource at /mcp/... and /agent-proxy/...). |
/.well-known/oauth-authorization-server | OAuth | RFC 8414 AS metadata — blended: authorization/token endpoints point at Hydra (hydra.publicUrl), registration at ShellWatch’s /api/hydra/register. Advertises mcp, agent (if enabled), and offline_access scopes. |
/health | plain HTTP | Health check (200 = up). |
Served by Hydra (at hydra.publicUrl, not port 3000):
| Path | Purpose |
|---|---|
/oauth2/auth | Authorization endpoint (authorization_code + PKCE) |
/oauth2/token | Token endpoint — access + refresh tokens |
/oauth2/revoke | Token revocation |
/oauth2/sessions/logout | RP-initiated logout |
/.well-known/jwks.json | Signing keys |
Auth at a glance
Section titled “Auth at a glance”All bearer tokens are opaque OAuth access tokens issued by Hydra; ShellWatch resolves them via introspection (cached, see hydra.introspectionCacheTtlMs). The web UI is a public PKCE client: it holds its access token in memory and authenticates the API the same way any other client does.
| Path family | Auth |
|---|---|
/, /session, /observer, /settings/*, /sign/:id, /audit/*, /admin/*, /register, /auth/callback | Public static SPA shell — when it has no token the SPA starts the OAuth flow (redirecting to Hydra, which redirects to the /api/hydra/login passkey page); the data behind these pages comes from /api/* (Bearer, ui scope). Admin pages additionally require the admin role server-side. |
/api/auth/register, /api/auth/register/options, /api/auth/passkey-status | Public (rate-limited) — anonymous first-registration bootstrap. |
/api/auth/sessions* | Bearer (ui). Single-client and revoke-all are additionally step-up gated (a fresh passkey assertion). |
/api/hydra/* | Public (rate-limited) — the passkey login/consent providers; they establish authentication, so they can’t require it. |
/api/webauthn/register, /api/webauthn/credentials/:id/revoke, /api/webauthn/credentials/:id/confirm | Bearer (ui) + a fresh step-up assertion. |
/api/webauthn/stepup/*, /api/webauthn/invite | Bearer (ui). (These mint or list credentials for step-up — they are not themselves step-up gated. Invite creation is safe without step-up because invite-registered credentials land in pending_confirmation and the confirm step is gated.) |
/passkey-invite/:token, /api/passkey-invite/* | Token-gated (the URL itself is the credential — single-use, short-lived). |
/api/version | Public — exposes build SHA + ref only (also baked into the SPA shell). |
/api/* (other) | Authorization: Bearer <token> with ui scope. Admin-only routes (e.g. /api/accounts) check the role. |
/ws | Bearer with ui scope, passed via Sec-WebSocket-Protocol: shellwatch.bearer, <token> (browsers can’t set an Authorization header on WebSocket upgrades). |
/mcp | Authorization: Bearer <token> with mcp scope. |
/agent-proxy | Authorization: Bearer <token> with agent scope. |
/api/hydra/register, /.well-known/oauth-* | Public (clients register before they have a token; rate-limited). |
/health | Public. |
The ui scope is granted only to the first-party SPA client — an mcp- or agent-scoped token cannot call /api/* or attach to /ws.
IP allowlisting
Section titled “IP allowlisting”Self-hosted deployments can apply a CIDR allowlist to /mcp and /agent-proxy via security.allowedNetworks; see Self-hosting → Configuration reference. All other paths rely on session/Bearer auth without CIDR filtering — your reverse proxy or firewall is the right place to add network-level gates if you want them everywhere.
WebSocket protocol (/ws)
Section titled “WebSocket protocol (/ws)”Client → Server: terminal:attach — start streaming a session's output to this socket terminal:input — keystrokes terminal:resize — cols/rows terminal:close — close the session terminal:take-control — take input control (other tabs go read-only) terminal:release-control
Server → Client: terminal:output — buffered + live output terminal:status — session state changes terminal:closed — session ended terminal:mode — input control mode changed sessions:changed — fired on any session lifecycle change (broadcast) sign:request — a PendingAction needs approval sign:resolved — a PendingAction was resolved (clears toasts) error — protocol or state errorSign approvals (resolve/deny with WebAuthn assertion) go over REST (/api/actions/:id/resolve), not the WebSocket.