# Models & Preferences What models are available to the current user and how they control visibility. --- ### Enabled Models The primary endpoint for populating model selectors: ``` GET /models/enabled ``` Returns a composite response: ```json { "data": [ UserModel, ... ], "default_model": "claude-sonnet-4-20250514" } ``` `default_model` is the admin-configured default model ID (from the `default_model` policy). Empty string if unset. **UserModel object:** ```json { "id": "composite-id", "display_name": "Claude Sonnet 4", "model_id": "claude-sonnet-4-20250514", "model_type": "chat", "source": "catalog", "provider_config_id": "uuid", "config_id": "uuid", "provider_name": "Anthropic", "provider_type": "anthropic", "capabilities": { "streaming": true, "tool_calling": true, "vision": true, "thinking": true, "reasoning": false, "code_optimized": false, "web_search": false, "max_context": 200000, "max_output_tokens": 8192 }, "is_persona": false, "persona_id": "", "persona_handle": "", "persona_scope": "", "persona_avatar": "", "persona_team_name": "", "description": "", "icon": "", "avatar": "", "system_prompt": "", "temperature": null, "max_tokens": null, "tool_grants": [], "pricing": { "input_per_m": 3.00, "output_per_m": 15.00, "currency": "USD" }, "scope": "global", "owner_id": null, "team_name": "", "hidden": false, "sort_order": 0, "provider_status": "healthy" } ``` **Field reference:** | Field | Type | Description | |-------|------|-------------| | `id` | string | Composite key. Catalog: `{config_id}:{model_id}`. Persona: persona UUID. | | `display_name` | string | Human-readable name (from catalog or persona). | | `model_id` | string | Raw model identifier (e.g. `claude-sonnet-4-20250514`). | | `model_type` | string | `"chat"`, `"embedding"`, `"image"`, etc. | | `source` | string | `"catalog"` (DB catalog entry) or `"persona"`. | | `provider_config_id` | string | UUID of the provider config that serves this model. | | `config_id` | string | Alias of `provider_config_id` (frontend compat). | | `provider_name` | string | Display name of the provider config. | | `provider_type` | string | Provider type slug (e.g. `"anthropic"`, `"openai"`). | | `capabilities` | object | Nested `ModelCapabilities` — see below. | | `is_persona` | bool | `true` if this entry is a Persona, not a raw model. | | `persona_id` | string | Persona UUID (omitted if not a persona). | | `persona_handle` | string | `@handle` for mention routing (omitted if not a persona). | | `persona_scope` | string | Persona scope: `"global"`, `"team"`, `"personal"` (omitted if not). | | `persona_avatar` | string | Avatar URL (omitted if not a persona or unset). | | `persona_team_name` | string | Owning team name for team-scoped personas (omitted if N/A). | | `description` | string | Persona description (omitted if empty). | | `icon` | string | Persona icon (omitted if empty). | | `avatar` | string | Avatar URL (omitted if empty). | | `system_prompt` | string | Persona system prompt (omitted if empty). | | `temperature` | float\|null | Persona temperature override. | | `max_tokens` | int\|null | Persona max-tokens override. | | `tool_grants` | string[] | Tool IDs granted to this persona. | | `pricing` | object\|null | Nested `ModelPricing` — see below. Omitted if unavailable. | | `scope` | string | `"global"`, `"team"`, or `"personal"`. | | `owner_id` | string\|null | Owner user/team ID for team/personal scoped entries. | | `team_name` | string | Team name (persona entries only). | | `hidden` | bool | `true` if the user has hidden this model via preferences. | | `sort_order` | int | User's custom sort position (0 = default). | | `provider_status` | string | Health status: `"healthy"`, `"degraded"`, `"down"`, `"unknown"`, or empty. | **ModelCapabilities:** | Field | Type | Description | |-------|------|-------------| | `streaming` | bool | Supports streaming responses. | | `tool_calling` | bool | Supports function/tool calling. | | `vision` | bool | Supports image inputs. | | `thinking` | bool | Supports extended thinking. | | `reasoning` | bool | Reasoning-optimized model. | | `code_optimized` | bool | Code-optimized model. | | `web_search` | bool | Has built-in web search. | | `max_context` | int | Context window size in tokens. | | `max_output_tokens` | int | Maximum output tokens. | Capabilities are resolved through the three-tier chain: catalog DB → heuristic inference → admin overrides (see `providers.md` §capabilities). **ModelPricing:** | Field | Type | Description | |-------|------|-------------| | `input_per_m` | float | Input cost per million tokens. | | `output_per_m` | float | Output cost per million tokens. | | `currency` | string | Currency code (e.g. `"USD"`). | **Persona inheritance:** When `is_persona` is `true`, the `capabilities` and `pricing` objects are inherited from the persona's underlying base model. The persona adds identity (name, handle, avatar, system prompt, tool grants) — not capability restrictions. Persona entries always have `hidden: false`; persona visibility is controlled by the grant system and the `is_active` flag, not by model preferences. **Model allowlist filtering (v0.24.2):** Non-admin users whose groups define model restrictions will only see models in their allowlist. Persona entries whose underlying `model_id` is not in the allowlist are also filtered. Admins bypass this filter entirely. **Visibility resolution order:** 1. Global catalog: enabled models → all authenticated users 2. Team catalog: enabled models → team members 3. Personal BYOK: enabled models → owner (requires `allow_user_byok` policy) 4. Personas: global + team + personal + shared-via-grants 5. User hidden preferences applied last (sets `hidden` flag, does not remove) 6. Model allowlist filtering (removes entries entirely for non-admins) --- ### User Model Preferences Users can hide models they don't want to see and set per-model defaults. Preferences are keyed on the **composite identity** `provider_config_id:model_id` — the same model from different providers can have independent visibility. ``` GET /models/preferences → { "data": [PreferenceEntry, ...] } PUT /models/preferences ← { "model_id": "...", "provider_config_id": "uuid", ... } POST /models/preferences/bulk ← { "entries": [...], "hidden": bool } ``` **GET /models/preferences** Returns all preference entries for the authenticated user. PreferenceEntry: ```json { "id": "uuid", "user_id": "uuid", "model_id": "grok-4.1-fast", "provider_config_id": "uuid", "hidden": true, "preferred_temperature": 0.7, "preferred_max_tokens": 4096, "sort_order": 0, "created_at": "2025-06-15T14:30:00Z", "updated_at": "2025-06-15T14:30:00Z" } ``` **PUT /models/preferences** Upserts a single preference entry. All fields except `model_id` are optional — only provided fields are updated. If no entry exists, one is created. Request body: ```json { "model_id": "claude-sonnet-4-20250514", "provider_config_id": "uuid", "hidden": true, "preferred_temperature": 0.7, "preferred_max_tokens": 4096, "sort_order": 1 } ``` `model_id` and `provider_config_id` are required. All other fields are optional — only provided fields are updated. If no entry exists, one is created. Returns `{ "message": "preference updated" }`. **POST /models/preferences/bulk** Sets the hidden state for multiple model+provider pairs at once. The `hidden` value is applied to **all** entries in the batch — this is not per-entry hidden control. Request body: ```json { "entries": [ { "model_id": "grok-4.1-fast", "provider_config_id": "uuid" }, { "model_id": "llama-3.1-70b", "provider_config_id": "uuid" } ], "hidden": true } ``` Returns `{ "message": "preferences updated", "count": 2 }`. **Identity rule:** `provider_config_id` is required on write. The same bare `model_id` from two different provider configs (e.g. global Venice vs personal BYOK Venice) are independent preference entries. The frontend composite ID format is `{provider_config_id}:{model_id}`. **Persona preferences:** Personas are not in this table. A persona's visibility is controlled by the grant system and the `is_active` flag, not by model preferences. ---