# User Profile & Settings User identity, preferences, and credentials. **All endpoints require:** `Auth()` middleware (JWT bearer token). --- ### Get Profile ``` GET /profile ``` **Auth:** Authenticated user Returns the current user's profile. ```json { "id": "uuid", "username": "jdoe", "email": "jdoe@example.com", "display_name": "Jane Doe", "role": "user", "avatar": "data:image/png;base64,...", "settings": { "theme": "dark" }, "created_at": "2025-06-15T14:30:00Z", "last_login_at": "2025-06-15T14:30:00Z" } ``` | Field | Type | Notes | |-------|------|-------| | `id` | string | UUIDv4 | | `username` | string | Unique login name | | `email` | string | Unique, lowercased | | `display_name` | string \| null | Optional friendly name | | `role` | `"user"` \| `"admin"` | Platform role | | `avatar` | string \| null | Data URI (PNG, 128×128). Omitted if unset. | | `settings` | object | User preferences (theme, keybindings, etc.) | | `created_at` | string | ISO 8601 timestamp | | `last_login_at` | string \| null | ISO 8601 timestamp. Null if never logged in. | > **Note:** The profile endpoint returns `avatar` (not `avatar_url`) as a > curated response shape. The `User` model uses `avatar_url` internally. --- ### Update Profile ``` PUT /profile ``` **Auth:** Authenticated user ```json { "display_name": "New Name", "email": "new@example.com" } ``` Both fields are optional (partial update). Returns the full profile (same shape as `GET /profile`). **Errors:** - `409` — email already taken --- ### Upload Avatar ``` POST /profile/avatar ``` **Auth:** Authenticated user **Content-Type:** `application/json` ```json { "image": "data:image/png;base64,..." } ``` Accepts a base64 data URI or raw base64 string. The server decodes, resizes to 128×128 PNG, and stores as a data URI. Max input: 2 MB. **Response:** ```json { "avatar": "data:image/png;base64,..." } ``` **Errors:** - `400` — missing `image` field, invalid base64, unsupported format, too large --- ### Delete Avatar ``` DELETE /profile/avatar ``` **Auth:** Authenticated user **Response:** ```json { "message": "avatar removed" } ``` --- ### Change Password ``` POST /profile/password ``` **Auth:** Authenticated user (builtin auth only) ```json { "current_password": "old-pass", "new_password": "new-pass-min-8" } ``` Validates `current_password` against stored bcrypt hash. `new_password` must be 8–128 characters. On success, the UEK (User Encryption Key) is re-wrapped with the new password so BYOK-encrypted provider keys remain accessible. **Response:** ```json { "message": "password updated" } ``` **Errors:** - `400` — missing fields, password too short/long - `401` — current password incorrect --- ### Get Settings ``` GET /settings ``` **Auth:** Authenticated user Returns user-level preferences wrapped in a `settings` key. ```json { "settings": { "theme": "dark", "editor_keybindings": "vim", "default_model": "claude-sonnet-4-5" } } ``` This is a **composite response** (named key wrapping a single object), not a bare object. The `settings` key is always present; its value is `{}` if the user has never saved preferences. --- ### Update Settings ``` PUT /settings ``` **Auth:** Authenticated user ```json { "theme": "light", "new_key": "new_value" } ``` Shallow merge: incoming keys overwrite existing, unmentioned keys are preserved. Handles edge cases: SQL NULL, JSON `null`, and corrupted array values are all normalized to `{}` before merge. Returns the updated settings (same shape as `GET /settings`). --- ### Get My Permissions ``` GET /profile/permissions ``` **Auth:** Authenticated user Returns the current user's resolved permission set, contributing group IDs, team memberships, and UI-relevant policies. Admin users receive all permissions by definition. **Response:** ```json { "permissions": ["model.use", "kb.read", "channel.create"], "groups": ["group-id-1", "00000000-0000-0000-0000-000000000001"], "teams": [ { "id": "uuid", "name": "Engineering", "my_role": "member" } ], "policies": { "allow_user_byok": false, "allow_user_personas": false, "allow_raw_model_access": true, "kb_direct_access": true } } ``` | Field | Type | Description | |-------|------|-------------| | `permissions` | string[] | Resolved permission set (union of all group permissions) | | `groups` | string[] | Contributing group IDs (always includes Everyone) | | `teams` | object[] | User's active team memberships with `my_role` | | `policies` | object | Boolean policy flags relevant to UI feature gating | `permissions` is sorted alphabetically. `teams` includes only active teams the user is a member of. This endpoint is the SDK's bootstrap source for `sw.can(permission)`. Called once at login and on each token refresh. ---