# Providers & Routing The **multi-provider** system. One or more LLM providers are configured, each with their own API keys, endpoints, and model catalogs. The routing layer decides which provider handles each request. ### User BYOK Provider Configs Gated by `allow_user_byok` policy. Personal API keys are encrypted with the user's UEK (per-user encryption key, Argon2id-derived). Platform admins cannot recover personal keys. ``` GET /api-configs → { "configs": [safeConfig objects] } POST /api-configs ← { "name", "provider", "endpoint", "api_key", ... } GET /api-configs/:id → safeConfig PUT /api-configs/:id ← partial update (api_key optional) DELETE /api-configs/:id ``` `safeConfig` — API keys are **never** returned: ```json { "id": "uuid", "name": "My OpenAI", "provider": "openai", "endpoint": "https://api.openai.com/v1", "model_default": "gpt-4o", "scope": "personal", "owner_id": "uuid", "is_active": true, "has_key": true, "config": {}, "headers": {}, "settings": {}, "created_at": "..." } ``` **List models for a user config:** ``` GET /api-configs/:id/models ``` Returns `{ "models": [catalog entries] }`. **Fetch/sync models from provider API:** ``` POST /api-configs/:id/models/fetch ``` Calls the provider's model list API, upserts into the local catalog. ### Admin Global Provider Configs ``` GET /admin/configs → { "configs": [configWithKey objects] } POST /admin/configs ← { "name", "provider", "endpoint", "api_key", "config", "headers", "settings", "is_private" } PUT /admin/configs/:id ← partial update DELETE /admin/configs/:id ``` `configWithKey` is the same as `safeConfig` but comes from `ListGlobal` — still redacts API keys, just adds the `has_key` flag. Admin configs use the `ENCRYPTION_KEY` env var (not per-user UEK). `is_private`: when true, the config is available for admin-created Personas but not directly selectable by users. ### Model Catalog (Admin) The catalog is populated by fetching from provider APIs and stores model metadata (capabilities, context window, pricing). ``` GET /admin/models → { "models": [catalog entries] } PUT /admin/models/:id ← { "visibility", "display_name", ... } PUT /admin/models/bulk ← { "provider_config_id", "visibility" } DELETE /admin/models/:id POST /admin/models/fetch ← { "provider_config_id": "uuid|empty" } ``` **Fetch** with empty `provider_config_id` syncs ALL active global providers. Returns: ```json { "message": "models synced", "added": 5, "updated": 12, "total": 47 } ``` Or for multi-provider fetch: `{ "added", "updated", "total", "errors": [...] }`. **Bulk visibility** sets all models for a provider (or all models globally if no `provider_config_id`) to the specified visibility (`enabled`, `disabled`, `team`). ### Provider Health Real-time health tracking per provider config. The health accumulator records success/failure/latency on every completion, flushes to DB every 60 seconds. **Get all:** ``` GET /admin/providers/health ``` Returns `{ "data": [ProviderHealth objects] }`: ```json { "provider_config_id": "uuid", "provider_config_name": "OpenAI Production", "status": "healthy|degraded|down", "error_rate": 0.02, "avg_latency_ms": 450, "timeout_rate": 0.01, "rate_limit_count": 3, "last_check": "...", "last_error": "...|null" } ``` **Get single:** ``` GET /admin/providers/:id/health ``` **Auto-disable:** After `PROVIDER_AUTO_DISABLE_THRESHOLD` consecutive "down" windows (default: 3), the provider is automatically deactivated. ### Capability Overrides Admin can override any model capability detected by the catalog or heuristic layer. ``` GET /admin/capability-overrides → { "data": [...] } GET /admin/models/:id/capabilities → capabilities for one model PUT /admin/models/:id/capabilities ← { "field": "value" } DELETE /admin/models/:id/capabilities/:overrideId ``` Override fields: `supports_vision`, `supports_tools`, `supports_thinking`, `context_window`, `max_output_tokens`, etc. ### Routing Policies Policy-based request routing. Evaluated after model/config resolution, before provider dispatch. **Admin CRUD:** ``` GET /admin/routing/policies → { "data": [...] } GET /admin/routing/policies/:id → policy object POST /admin/routing/policies ← { "name", "scope", "team_id", "priority", "policy_type", "config", "is_active" } PUT /admin/routing/policies/:id DELETE /admin/routing/policies/:id ``` Policy object: ```json { "id": "uuid", "name": "Prefer Anthropic", "scope": "global|team", "team_id": "uuid|null", "priority": 10, "policy_type": "provider_prefer|team_route|cost_limit|model_alias|capability_match", "config": {}, "is_active": true } ``` | Policy type | Config | Behavior | |------------|--------|----------| | `provider_prefer` | `{ "providers": ["cfg-1", "cfg-2"] }` | Ordered fallback list | | `team_route` | `{ "providers": ["cfg-1"] }` | Restrict team to specific providers | | `cost_limit` | `{ "max_cost_per_request": 0.50 }` | Heuristic cost cap | | `model_alias` | `{ "alias": "fast", "target_model": "...", "target_config": "..." }` | Alias → provider+model rewrite | | `capability_match` | `{ "require": ["tool_calling"], "prefer": "cheapest" }` | Match cheapest model with required capabilities | **Dry-run test:** ``` POST /admin/routing/test ``` ```json { "model": "claude-sonnet-4-20250514", "user_id": "uuid", "team_id": "uuid|null" } ``` Returns the ranked candidate list with health status for each. ### Provider Types Registry of supported provider types with metadata. ``` GET /admin/provider-types ``` Returns `{ "types": [...] }` with name, display name, default endpoint, profile schema, and supported features per type. ---