Skip to content

Authentication

Hermes Web UI uses one shared secret per instance — a server password — and a session cookie. There is no per-user account system: whoever holds the password (or is admitted by your reverse proxy) is the operator, and gets the full API surface. This keeps a single-user, self-hosted deployment simple, and every client authenticates the same way.

The model at a glance

Mechanism Use when
No auth The instance has no password set (password_required: false). Call the API directly.
Password → session cookie The default. POST /api/auth/login with the password; the server sets a hermes_session cookie you replay on every request.
Custom headers The instance is behind an authenticating reverse proxy (Authentik, Cloudflare Access) or a token gate. Attach headers like Authorization: Bearer … to every request.

Probe first: /api/auth/status

Always public. Tells a client which path to take.

GET /api/auth/status
{ "authenticated": false, "password_required": true }

  • password_required: false → open instance, no login needed.
  • authenticated: true → your current cookie/headers are already valid.
  • authenticated: false + password_required: true → log in.

Password login: /api/auth/login

POST /api/auth/login
Content-Type: application/json

{ "password": "your-server-password" }

On success (HTTP 200) the response carries a Set-Cookie: hermes_session=… header. Store it and send it on every subsequent request. On a wrong password the server returns 401 (and rate-limits repeated attempts).

  • Cookie name: hermes_session by default. Operators running multiple instances on one hostname can override it via HERMES_WEBUI_COOKIE_NAME, so a robust client reads the cookie name from the Set-Cookie response rather than hard-coding it.
  • Logout: POST /api/auth/logout clears the session.

Native clients

Use a cookie-persisting HTTP session and login is transparent after the first call:

  • iOS / SwiftURLSession with the default HTTPCookieStorage stores and replays hermes_session automatically. (The reference client hermex does exactly this.)
  • Android / Kotlin — add a JavaNetCookieJar(CookieManager()) to your OkHttpClient.
  • Browser / JS — use fetch(url, { credentials: "include" }) (same-origin) so the cookie rides along.

Custom headers (proxied / token-gated deployments)

Some self-hosters put Hermes behind an authenticating proxy or a token gate that the password flow can't satisfy. For those, attach one or more fixed headers to every outgoing request:

Authorization: Bearer <token>
X-Api-Key: <key>

The reference iOS client persists these in the Keychain and injects them on all requests (see its CustomHeader type). Security note for client authors: do not forward Authorization (and other sensitive headers) across a cross-origin redirect — strip them if the server redirects off-origin, to avoid leaking the secret.

What requires auth

Nearly the entire /api/* surface requires a valid session when a password is set. A small set of paths are always public: /api/auth/login, /api/auth/status, static assets under /static/, and the login page. Everything else in this reference assumes an authenticated request.

Errors

Status Meaning Client action
401 Not authenticated / wrong password Prompt for the password, call /api/auth/login, retry
403 Authenticated but the action is refused Surface the message; do not retry blindly
429 Too many login attempts Back off and tell the user to wait

See Conventions for the standard error body shape.