Friends
Add friends, share sessions with people you trust, and discover users by username.
Friends is a social layer on top of Happier sessions:
- Discover people by a username (
@username) - Send friend requests and manage relationships
- Share sessions directly with friends
The server advertises Friends and GitHub OAuth availability via GET /v1/features. The UI uses that endpoint to decide whether to show Friends at all, and whether to offer GitHub connect.
Identity model (one username, regardless of how you got it)
Happier uses a single, unique identifier for discovery: account.username.
- User search (
GET /v1/user/search) matchesusername - Friend requests use account IDs internally, but the UI is centered around
@username - Usernames are normalized to lowercase on the server
Users should not need to know whether a username came from GitHub or was set manually — they just search @username.
GitHub connection (OAuth)
When a user connects GitHub:
- The server completes the OAuth flow and fetches
GET https://api.github.com/user(scope isread:user). - The server links the GitHub account to the Happier account (1 GitHub account → 1 Happier account).
- The server does not overwrite an existing
account.username. - If
account.usernameis empty, the server tries to set it to the GitHublogin(lowercased), but only if that username is not already taken.
If the GitHub login username is already taken (or invalid under your server’s username policy), GitHub can still connect successfully, but the app will prompt the user to pick a different @username before Friends setup is complete.
Disconnecting GitHub:
- Always removes the GitHub link.
- Keeps a custom username.
- Clears
account.usernameonly when it matches the GitHub login (treated as “GitHub-derived”).
Username-only mode (no GitHub required)
Some self-hosted servers prefer not to require GitHub. When enabled, users can claim a local username.
- Endpoint:
POST /v1/account/username - Validation is controlled by server env vars (see below).
Username selection on GitHub-required servers
Even when Friends requires GitHub, the app may still ask a user to choose a username.
This happens when the user’s GitHub login cannot be used as a unique server username (for example, it’s already taken). In that case:
- GitHub can still be connected successfully.
- The user must pick a different
@usernameto be discoverable. - The app surfaces this on the Friends screens, and in Settings → Account.
Server configuration
Feature toggles
HAPPIER_FEATURE_SOCIAL_FRIENDS__ENABLED(true/false, defaulttrue): master switch for Friends endpoints + UI.HAPPIER_FEATURE_SOCIAL_FRIENDS__ALLOW_USERNAME(true/false, defaultfalse):false: Friends requires GitHub connection for requests/discovery.true: Friends requires a claimed@username(GitHub is optional).
Username policy (when username-based friends are enabled)
FRIENDS_USERNAME_MIN_LEN(default3)FRIENDS_USERNAME_MAX_LEN(default32)FRIENDS_USERNAME_REGEX(default^[a-z0-9_-]+$)
GitHub OAuth
Required to enable GitHub connect:
GITHUB_CLIENT_IDGITHUB_CLIENT_SECRETGITHUB_REDIRECT_URL(canonical) orGITHUB_REDIRECT_URI(legacy)
Optional:
HAPPIER_WEBAPP_URL(defaulthttps://app.happier.dev): base URL/scheme for the client. The server redirects to${HAPPIER_WEBAPP_URL}/oauth/githubafter the server callback completes.- This is only “optional” if you are using the hosted client at
https://app.happier.dev. If you are self-hosting the web app (or using a local dev web UI), setHAPPIER_WEBAPP_URL(orHAPPIER_WEBAPP_OAUTH_RETURN_URL_BASE) so OAuth can return to your client.
- This is only “optional” if you are using the hosted client at
GITHUB_STORE_ACCESS_TOKEN(true/false, defaultfalse): whether to persist GitHub access tokens on the server (Friends does not require this). If you enforce org membership viaAUTH_GITHUB_ORG_MEMBERSHIP_SOURCE=oauth_user_token(membership checks via user OAuth token mode), token storage is required for offboarding checks. See/docs/deployment/env#github-org-membership-source-recommended-github-app.OAUTH_PENDING_TTL_SECONDS(default600, min60, max3600): how long a “pending OAuth finalize” (e.g. waiting for username selection) can be finalized.- Legacy alias:
GITHUB_OAUTH_PENDING_TTL_SECONDS(ifOAUTH_PENDING_TTL_SECONDSis unset).
- Legacy alias:
GitHub setup (OAuth App)
- In GitHub, go to Settings → Developer settings → OAuth Apps → New OAuth App.
- Set Authorization callback URL to your server callback endpoint:
https://YOUR_SERVER/v1/oauth/github/callback- You do not need GitHub’s “Device Flow / device authentication flow”. Happier uses the standard browser redirect OAuth flow, so you can leave “Enable Device Flow” disabled.
- Copy the Client ID and Client secret into your server environment as
GITHUB_CLIENT_IDandGITHUB_CLIENT_SECRET. - Set
GITHUB_REDIRECT_URLto the same callback URL you registered above. - (Self-hosting) Set
HAPPIER_WEBAPP_URLto the URL/scheme that should receive the post-OAuth redirect.- The server redirects to
${HAPPIER_WEBAPP_URL}/oauth/github?...
- The server redirects to
Client behavior (UI)
- The Friends experience is reachable at
/friends(legacy/inboxredirects to/friends). - If Friends is disabled (
features.social.friends.enabled=false), the UI hides the Friends tab and related screens. - If Friends requires GitHub (
features.social.friends.requiresGitHub=true) but GitHub OAuth isn’t configured (features.oauth.providers.github.configured=false), the UI disables the GitHub connect button and shows a clear “not configured” hint.
API surface (high level)
GET /v1/features: server-advertised feature flags (Friends + GitHub OAuth).GET /v1/user/search?query=...: search users by username.POST /v1/friends/add: send a friend request.POST /v1/friends/remove: remove a friend.GET /v1/friends: list friends/relationships.POST /v1/account/username: claim/update a username (requires auth; available when Friends is enabled and either username-based friends are enabled or GitHub is connected).- Body:
{ "username": "alice" } - Success:
200 { "username": "alice" } - Errors:
400 { "error": "friends-disabled" }when Friends is disabled400 { "error": "username-disabled" }when username-based friends are disabled and the user has no GitHub identity400 { "error": "invalid-username" }when the username fails validation409 { "error": "username-taken" }when the username is already in use
- Body: