Skip to content

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.

PathTypePurpose
/staticWeb UI — terminal view (SvelteKit SPA)
/session/:idstaticWeb UI — specific session terminal
/observerstaticWeb UI — multi-session grid
/sign/:idstaticWeb UI — sign-request approval
/registerstaticWeb UI — WebAuthn registration (gated by selfRegistrationEnabled)
/passkey-invite/:tokenstaticToken-gated cross-device passkey enrollment
/settings/*staticWeb UI — general, endpoints, keys, passkeys, sessions, notifications
/audit/*staticWeb UI — /audit/sessions, /audit/signings
/admin/*staticWeb UI — /admin/accounts, /admin/general (admins only)
/auth/callbackstatic (SPA)The web UI’s OAuth redirect URI — handled client-side, not a server route
/api/*RESTEndpoints, sessions, keys, passkeys, accounts, actions, audit, push
/api/versionRESTBuild SHA + ref. Public; same payload also embedded into the SPA shell.
/api/webauthn/*RESTWebAuthn 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/optionsRESTIssue a step-up assertion challenge (/api/webauthn/stepup/verify accepts the response).
/api/webauthn/inviteRESTIssue a cross-device passkey-enrollment token (POST); inspect the live token (GET). Step-up gated.
/api/webauthn/credentials/:id/confirmRESTConfirm a credential registered via the invite flow. Step-up gated.
/api/passkey-invite/:tokenRESTValidate an invite token (server-side).
/api/passkey-invite/register/optionsRESTToken-gated registration options for the invite flow.
/api/passkey-invite/registerRESTToken-gated registration verification for the invite flow.
/api/actions/:idRESTGET action context, POST .../resolve, POST .../deny
/api/sessions/:id/tailRESTSnapshot of a session’s recent output (used by the agent-forwarding sign page)
/api/audit/sessionsRESTKeyset-paginated session-lifecycle audit log (account-scoped)
/api/audit/signingsRESTKeyset-paginated signing-request audit log (account-scoped)
/api/auth/sessionsRESTGET lists the account’s authorized OAuth clients (web UI, MCP, agent) with scopes + last-authorization time. Backs Settings → Sessions.
/api/auth/sessions/:clientIdRESTDELETE revokes one client’s tokens/grant (step-up gated).
/api/auth/sessions/revoke-allRESTPOST revokes every consent grant + login session for the account — signs out everywhere (step-up gated).
/wsWebSocketTerminal I/O + sessions:changed + sign:request / sign:resolved
/mcpMCP (streamable HTTP)Model Context Protocol — Bearer auth, mcp scope
/agent-proxyWebSocketSSH agent proxy — Bearer auth, agent scope
/api/hydra/registerOAuth (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/loginOAuth (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/consentOAuth (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/logoutOAuth (provider)RP-initiated logout callback. Requires an id_token_hint — an unhinted (CSRF) logout is rejected rather than silently terminating the session.
/api/hydra/errorOAuth (provider)OAuth error landing page.
/.well-known/oauth-protected-resourceOAuthRFC 9728 protected-resource metadata (also per-resource at /mcp/... and /agent-proxy/...).
/.well-known/oauth-authorization-serverOAuthRFC 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.
/healthplain HTTPHealth check (200 = up).

Served by Hydra (at hydra.publicUrl, not port 3000):

PathPurpose
/oauth2/authAuthorization endpoint (authorization_code + PKCE)
/oauth2/tokenToken endpoint — access + refresh tokens
/oauth2/revokeToken revocation
/oauth2/sessions/logoutRP-initiated logout
/.well-known/jwks.jsonSigning keys

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 familyAuth
/, /session, /observer, /settings/*, /sign/:id, /audit/*, /admin/*, /register, /auth/callbackPublic 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-statusPublic (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/confirmBearer (ui) + a fresh step-up assertion.
/api/webauthn/stepup/*, /api/webauthn/inviteBearer (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/versionPublic — 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.
/wsBearer with ui scope, passed via Sec-WebSocket-Protocol: shellwatch.bearer, <token> (browsers can’t set an Authorization header on WebSocket upgrades).
/mcpAuthorization: Bearer <token> with mcp scope.
/agent-proxyAuthorization: Bearer <token> with agent scope.
/api/hydra/register, /.well-known/oauth-*Public (clients register before they have a token; rate-limited).
/healthPublic.

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.

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.

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 error

Sign approvals (resolve/deny with WebAuthn assertion) go over REST (/api/actions/:id/resolve), not the WebSocket.