Skip to content

User Profile Management

Each Arbitex user account has a profile with preferences, an optional avatar, and a display name. Profile settings are stored in the profile_settings JSONB column on the users table and are scoped per user — administrators cannot see or modify individual user preferences from the admin panel.

The profile_settings blob controls per-user UI and chat defaults. Fields:

FieldTypeDefaultDescription
default_model_idstring | nullnullModel ID pre-selected for new conversations
preferred_modelsstring[][]Ordered list of favourite model IDs
default_summarizer_model_idstring | nullnullSynthesizer model used in Summarize mode
theme"light" | "dark" | "system""system"UI colour scheme
avatar_urlstring | nullnullPath to the user’s avatar image
GET /api/users/me/preferences
Authorization: Bearer <token>

Response 200 OK:

{
"default_model_id": "gpt-4o",
"preferred_models": ["gpt-4o", "claude-opus-4-6"],
"default_summarizer_model_id": null,
"theme": "dark",
"avatar_url": "/avatars/a1b2c3d4-...uuid....png"
}

If profile_settings is null in the database, defaults are returned without error.

PUT /api/users/me/preferences
Authorization: Bearer <token>
Content-Type: application/json
{
"default_model_id": "claude-sonnet-4-6",
"preferred_models": ["claude-sonnet-4-6", "gpt-4o"],
"theme": "dark"
}

The update is a full replacement of the preferences object (not a partial merge at the HTTP level). The server merges body.model_dump() into the existing profile_settings dict before writing, so fields omitted from the request body retain their current values if null values are not sent explicitly.

Response 200 OK — returns the updated UserPreferences object.

Avatars are stored as 256×256 PNG files in frontend/public/avatars/ and served as static files by the Nginx container.

PATCH /api/users/me/avatar
Authorization: Bearer <token>
Content-Type: multipart/form-data
file=<image_data>

Accepted formats: PNG, JPEG Maximum file size: 1 MB Processing: Pillow resizes the image to 256×256 pixels (Image.LANCZOS) and re-encodes it as PNG.

Stored path: frontend/public/avatars/{user_id}.png avatar_url value: /avatars/{user_id}.png

Error responses:

StatusCondition
400Content type is not image/png or image/jpeg
400File exceeds 1 MB
400Image cannot be decoded or resized (corrupt file)

Response 200 OK — returns the updated UserPreferences object (including the new avatar_url).

{
"default_model_id": "gpt-4o",
"preferred_models": [],
"default_summarizer_model_id": null,
"theme": "system",
"avatar_url": "/avatars/a1b2c3d4-...uuid....png"
}
DELETE /api/users/me/avatar
Authorization: Bearer <token>

Response 204 No Content

The image file at frontend/public/avatars/{user_id}.png is deleted from disk, and avatar_url is cleared from profile_settings. If no avatar was set, the endpoint is a no-op and still returns 204.

The theme field in UserPreferences is the per-user override for UI appearance. It maps to the same values accepted by the global theme system:

ValueBehaviour
"light"Always use light appearance
"dark"Always use dark appearance
"system"Follow OS prefers-color-scheme media query

The theme is stored in profile_settings.theme and also mirrored to localStorage key arbitex-theme by the frontend. When the user changes their theme via the UI toggle, the preference is persisted via PUT /api/users/me/preferences.

See Theme Configuration for the full CSS variable reference and admin custom theme upload.

Setting default_model_id pre-populates the model selector for new conversations. If the model is no longer enabled (e.g., an admin deactivated the provider), the frontend falls back to the first available model in the catalog.

preferred_models is an ordered list that the frontend can use to render a pinned model list. The platform does not enforce this list; it is advisory for the UI.

default_summarizer_model_id is the synthesis model used when a conversation is created in Summarize mode. If unset, the frontend’s Summarize mode defaults to the first available model or a configured admin default.

In Phase 2 (SCIM v2 provisioning), the following profile fields will be populated from the identity provider on each sync:

  • display_name — pulled from the IdP displayName attribute (Okta) or displayName (Entra ID)
  • preferred_username — mapped from IdP userName
  • department, title — from standard SCIM User.name and enterprise extension attributes

Once directory sync is active for an organization, manually-set display names in Arbitex will be overwritten on the next sync cycle unless the field is marked as “locally managed” in the SCIM attribute mapping configuration.

See SCIM Provisioning for directory sync setup.

  • Avatars are served from the static public/avatars/ directory. The filename is always {user_id}.png (UUID-based), so there is no user-controlled filename component.
  • Uploaded files are re-encoded by Pillow before writing. This strips embedded metadata (EXIF, ICC profiles) and prevents polyglot files.
  • The profile_settings JSONB column is user-scoped. No user can read or write another user’s preferences via the /api/users/me/preferences endpoint; the server always uses the authenticated user’s identity from the JWT.