--- title: "Migrating to v1.5.0" description: "Breaking changes and migration instructions for the v1.5.0 release" --- v1.5.0 introduces several breaking changes across provider key configuration, Virtual Key semantics, the Go SDK, and the REST API. This page consolidates every breaking change with before/after examples and a migration checklist. **Make a database backup before upgrading.** Automatic database migrations run on startup and are not revertible. A backup is the only way to restore a previous state if anything goes wrong. A database successfully migrated to v1.5.0 cannot be used to run v1.4.x. --- ## Automatic Database Migration If you are running Bifrost with a database (SQLite or Postgres), existing data is automatically migrated on startup. You do not need to manually update your database records. The following automatic migrations run on upgrade: - Provider keys with `models: []` are converted to `models: ["*"]` - Virtual Key provider configs with `allowed_models: []` are converted to `allowed_models: ["*"]` - Virtual Keys with no `provider_configs` are backfilled with all currently configured providers (`allowed_models: ["*"]`, `key_ids: ["*"]`) - Virtual Keys with no `mcp_configs` are backfilled with all currently connected MCP clients (`tools_to_execute: ["*"]`) - Per-provider `deployments` maps (Azure, Bedrock, Vertex, Replicate) are migrated into the unified `aliases` field **The automatic migration only protects your existing data.** Any new configuration created after upgrading — via `config.json` or the REST API — must follow the new semantics described below. --- ## Breaking Change 1: Empty Array Now Means "Deny All" v1.5.0 flips the meaning of empty arrays across all allow-list fields: | What you write | v1.4.x meaning | v1.5.0 meaning | |---|---|---| | `[]` (empty array) | Allow **all** | Allow **none** | | `["*"]` (wildcard) | Not applicable | Allow **all** | | `["a", "b"]` | Only `a` and `b` | Only `a` and `b` (unchanged) | This affects four fields: | Field | Where | |---|---| | `models` | Provider key | | `allowed_models` | Virtual Key provider config | | `key_ids` | Virtual Key provider config | | `tools_to_execute` | Virtual Key MCP config | ### Provider key `models` **Before:** ```json { "value": "env.OPENAI_API_KEY", "models": [] } ``` `models: []` → key served all models **After:** ```json { "value": "env.OPENAI_API_KEY", "models": ["*"] } ``` ### Virtual Key `allowed_models` **Before:** ```json { "provider": "openai", "weight": 1.0 } ``` Missing `allowed_models` → all models allowed **After:** ```json { "provider": "openai", "allowed_models": ["*"], "key_ids": ["*"], "weight": 1.0 } ``` ### Virtual Key MCP `tools_to_execute` **Before:** ```json { "mcp_client_name": "my-tools", "tools_to_execute": [] } ``` **After:** ```json { "mcp_client_name": "my-tools", "tools_to_execute": ["*"] } ``` --- ## Breaking Change 2: `allowed_keys` Renamed to `key_ids` The field used to restrict which provider API keys a Virtual Key can use has been renamed from `allowed_keys` to `key_ids`. The deny-by-default rule also applies — omitting the field or setting it to `[]` now blocks all keys. Unlike `allowed_models`, there is no automatic database migration for `key_ids`. An empty or omitted `key_ids` disables all key selection. You must explicitly use `["*"]` to restore allow-all behavior. **Before:** ```json { "provider": "openai", "allowed_keys": ["key-prod-001"], "weight": 1.0 } ``` **After:** ```json { "provider": "openai", "key_ids": ["key-prod-001"], "allowed_models": ["*"], "weight": 1.0 } ``` To allow all keys: ```json { "provider": "openai", "key_ids": ["*"], "allowed_models": ["*"], "weight": 1.0 } ``` --- ## Breaking Change 3: Virtual Key `provider_configs` is Deny-by-Default In v1.4.x, a Virtual Key with no `provider_configs` had access to all providers. In v1.5.0, it blocks all providers. **Before:** `"provider_configs": []` → access to all providers **After:** `"provider_configs": []` → no provider access To allow all providers, list each one explicitly: ```json { "provider_configs": [ { "provider": "openai", "allowed_models": ["*"], "key_ids": ["*"], "weight": 1.0 }, { "provider": "anthropic", "allowed_models": ["*"], "key_ids": ["*"], "weight": 1.0 } ] } ``` The automatic migration backfills all currently configured providers into any VK that has an empty `provider_configs`. However, any VK created after upgrading must include explicit provider configs. --- ## Breaking Change 4: WhiteList Validation Two new validation rules are enforced on all allow-list fields. The API returns **HTTP 400** if either is violated. **Rule 1: Wildcard cannot be mixed with other values** ```json // ❌ Invalid { "allowed_models": ["*", "gpt-4o"] } // ✅ Valid { "allowed_models": ["*"] } ``` **Rule 2: No duplicate values** ```json // ❌ Invalid { "allowed_models": ["gpt-4o", "gpt-4o"] } ``` Applies to: `models`, `allowed_models`, `key_ids`, `tools_to_execute`, `tools_to_auto_execute`, `allowed_extra_headers`. --- ## Breaking Change 5: `weight` is Now Nullable The `weight` field on a Virtual Key provider config was previously a required `float64`. It is now an optional `*float64`. - `weight: 0.5` — provider participates in weighted load balancing - `weight: null` / omitted — provider is accessible for direct routing but excluded from weighted selection **API response change:** `weight` may now be `null`. Update any client code that assumes it is always a number. --- ## Breaking Change 6: Provider Keys API Separated Provider key management now has dedicated endpoints. The `keys` field has been removed from all provider API requests and responses. ### What changed | Before (v1.4.x) | After (v1.5.0) | |---|---| | `GET /api/providers/{p}` returns `keys` | `keys` field removed from provider response | | `POST /api/providers` accepts `keys` | `keys` field ignored — create keys separately | | `PUT /api/providers/{p}` accepts `keys` | `keys` field ignored — update keys via dedicated endpoints | ### New endpoints | Method | Endpoint | Description | |---|---|---| | `GET` | `/api/providers/{provider}/keys` | List all keys | | `GET` | `/api/providers/{provider}/keys/{key_id}` | Get a single key | | `POST` | `/api/providers/{provider}/keys` | Create a key | | `PUT` | `/api/providers/{provider}/keys/{key_id}` | Update a key | | `DELETE` | `/api/providers/{provider}/keys/{key_id}` | Delete a key | ### How to update **Creating a provider with keys:** **Before:** ```bash curl -X POST localhost:8080/api/providers -d '{ "provider": "openai", "keys": [{"name": "main", "value": "sk-..."}] }' ``` **After:** Create provider first, then add keys: ```bash curl -X POST localhost:8080/api/providers -d '{"provider": "openai"}' curl -X POST localhost:8080/api/providers/openai/keys -d '{"name": "main", "value": "sk-..."}' ``` **Reading keys:** **Before:** `curl localhost:8080/api/providers/openai | jq '.keys'` **After:** `curl localhost:8080/api/providers/openai/keys | jq '.keys'` **Updating / deleting keys:** **Before:** Bulk replace via provider update: ```bash curl -X PUT localhost:8080/api/providers/openai -d '{"keys": [{"id": "key-1", "value": "sk-new"}]}' ``` **After:** Individual key operations: ```bash curl -X PUT localhost:8080/api/providers/openai/keys/key-1 -d '{"name": "updated", "value": "sk-new"}' curl -X DELETE localhost:8080/api/providers/openai/keys/key-2 ``` --- ## Breaking Change 7: Compact Plugin Restructured The `enable_litellm_fallbacks` option has been removed and replaced with three granular options. **Before:** ```json { "compat": { "enable_litellm_fallbacks": true } } ``` **After:** ```json { "compat": { "convert_text_to_chat": true, "convert_chat_to_responses": true, "should_drop_params": true } } ``` | Old option | New option | Description | |---|---|---| | `enable_litellm_fallbacks` | `convert_text_to_chat` | Text completion → chat completion fallback | | _(new)_ | `convert_chat_to_responses` | Chat completion → Responses API fallback | | _(new)_ | `should_drop_params` | Drop unsupported OpenAI-compatible params | **Response field changes:** | Field | Change | |---|---| | `extra_fields.litellm_compat` | **Removed** | | `extra_fields.dropped_compat_plugin_params` | **Added** — lists params dropped by this plugin | | `extra_fields.converted_request_type` | **Added** — the request type it was converted to | --- ## Breaking Change 8: Replicate Image Edits Removed from Generations Endpoint The `/v1/images/generations` endpoint on the Replicate provider no longer accepts image editing parameters (source image, mask). It now only handles text-to-image generation. If you were passing image editing parameters to `/v1/images/generations` on Replicate, switch to `/v1/images/edits`. Support for image editing via `/v1/images/edits` on Replicate is also being removed in a follow-up release. Plan to migrate to an alternative provider. --- ## Breaking Change 9: Provider `deployments` removed — migrate to `aliases` Provider deployment mappings should now live in the top-level `aliases` field on each key. Aliases work across all providers and map any model name to a provider-specific identifier (deployment name, inference profile ARN, fine-tuned model ID, etc.). The database migration runs automatically on startup, migrating existing deployment data into `aliases`. Only `config.json` files need to be updated manually. ### Azure **Before:** ```json { "providers": { "azure": { "keys": [{ "value": "env.AZURE_API_KEY", "azure_key_config": { "endpoint": "env.AZURE_ENDPOINT", "deployments": { "gpt-4o": "my-gpt4o-deployment", "gpt-4o-mini": "my-mini-deployment" } } }] } } } ``` **After:** ```json { "providers": { "azure": { "keys": [{ "value": "env.AZURE_API_KEY", "azure_key_config": { "endpoint": "env.AZURE_ENDPOINT" }, "aliases": { "gpt-4o": "my-gpt4o-deployment", "gpt-4o-mini": "my-mini-deployment" } }] } } } ``` ### Bedrock `bedrock_key_config.deployments` is a legacy field and is **removed** in v1.5.0 config semantics. Some setups/builds may still accept it for backward compatibility, but do not rely on it — migrate to `aliases` to avoid silent breakage and future removal. **Before:** ```json { "bedrock_key_config": { "region": "env.AWS_REGION", "deployments": { "claude-3-5-sonnet": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0" } } } ``` **After:** ```json { "bedrock_key_config": { "region": "env.AWS_REGION" }, "aliases": { "claude-3-5-sonnet": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0" } } ``` ### Vertex **Before:** ```json { "vertex_key_config": { "project_id": "env.VERTEX_PROJECT_ID", "project_number": "env.VERTEX_PROJECT_NUMBER", "region": "env.VERTEX_REGION", "auth_credentials": "env.VERTEX_AUTH_CREDENTIALS", "deployments": { "gemini-2.0-flash": "projects/my-project/locations/us-central1/endpoints/123456" } } } ``` **After:** ```json { "vertex_key_config": { "project_id": "env.VERTEX_PROJECT_ID", "project_number": "env.VERTEX_PROJECT_NUMBER", "region": "env.VERTEX_REGION", "auth_credentials": "env.VERTEX_AUTH_CREDENTIALS" }, "aliases": { "gemini-2.0-flash": "projects/my-project/locations/us-central1/endpoints/123456" } } ``` ### Replicate The Replicate key config is also restructured. The `deployments` map is gone. A new boolean `use_deployments_endpoint` controls whether requests are routed through the [Deployments API](https://replicate.com/docs/reference/http#deployments.predictions.create) (private, fixed hardware) or the standard Models API. **Before:** ```json { "replicate_key_config": { "deployments": { "my-model": "owner/model-name/version-hash" } } } ``` **After:** ```json { "replicate_key_config": { "use_deployments_endpoint": true }, "aliases": { "my-model": "owner/model-name" } } ``` | Old field | New field | Notes | |---|---|---| | `replicate_key_config.deployments` | Removed | Use top-level `aliases` | | _(new)_ | `replicate_key_config.use_deployments_endpoint` | `bool`, default `false` | --- ## Breaking Change 10: Go SDK — `ExtraFields` Model Fields Renamed `ModelRequested string` has been replaced by two fields on `BifrostResponseExtraFields` and `BifrostErrorExtraFields`. **Before:** ```go model := response.ExtraFields.ModelRequested ``` **After:** ```go // The alias the caller passed as "model" in the request original := response.ExtraFields.OriginalModelRequested // The actual identifier sent to the provider API // Equals OriginalModelRequested when no alias is configured resolved := response.ExtraFields.ResolvedModelUsed ``` The same rename applies to `BifrostErrorExtraFields`. **JSON tag changes:** | Old | New | |---|---| | `"model_requested"` | `"original_model_requested"` + `"resolved_model_used"` | --- ## Breaking Change 11: Go SDK — `StreamAccumulatorResult` Field Renamed `Model string` has been replaced by two fields on `StreamAccumulatorResult` (returned by tracer streaming accumulation methods). **Before:** ```go result.Model ``` **After:** ```go result.RequestedModel // original alias from the caller result.ResolvedModel // actual model identifier used by the provider ``` --- ## Breaking Change 12: `selected_key_id` Cleared on Terminal Retry Failures With the introduction of multi-key retry rotation, `selected_key_id` (and `selected_key_name`) in the request context are **cleared when all retry attempts fail**. Previously, these fields always reflected the key that was selected for the request, even on error. The `attempt_trail` is now the authoritative record of every key tried and why each attempt failed. ### What changed | Field | Before | After | |---|---|---| | `selected_key_id` | Always set, even on error | Empty string when all retries exhausted | | `selected_key_name` | Always set, even on error | Empty string when all retries exhausted | | `attempt_trail` | Not present | Array of `{ key_id, key_name, fail_reason }` per attempt | ### Impact on logging and telemetry plugins The built-in **logging plugin** writes `selected_key_id` and `selected_key_name` directly to each log record. For multi-key requests that exhaust all retries, both fields will be empty in the stored log entry. The `attempt_trail` column captures the full per-attempt key history and is the correct field to use for failure attribution. The built-in **telemetry plugin** emits `selected_key_id` and `selected_key_name` as span attributes. For exhausted-retry failures these attributes will be empty strings on the error span. The `attempt_trail` span attribute contains the full rotation history. If you run a custom plugin or downstream log consumer that filters or groups by `selected_key_id` to track which key caused a failure, you must update it to handle the empty-string case and read from `attempt_trail` when attribution is needed. ### How to update **If you read `selected_key_id` from plugin context to attribute failed requests:** **Before:** ```go keyID, _ := ctx.Value(schemas.BifrostContextKeySelectedKeyID).(string) // keyID was always populated, even on error ``` **After:** ```go // Populated on success (or for single-key / pinned / sticky flows on error): keyID, _ := ctx.Value(schemas.BifrostContextKeySelectedKeyID).(string) // For full attribution across all retry attempts (including failures): if trail, ok := ctx.Value(schemas.BifrostContextKeyAttemptTrail).([]schemas.KeyAttemptRecord); ok { for _, record := range trail { // record.KeyID, record.KeyName, record.FailReason } } ``` **If you consume `selected_key_id` from the logging REST API:** The `selected_key_id` field on a `LogEntry` may now be an empty string when the request failed after exhausting all retries. Use `attempt_trail` for the full per-attempt key history. Single-key, pinned (`x-bf-key-id` / `x-bf-key-name`), and session-sticky requests are unaffected — they never rotate keys, so `selected_key_id` remains populated on failure for those flows. --- ## Opting Out: `version: 1` Compatibility Mode If you are not ready to adopt the new deny-by-default semantics, you can add a single field to `config.json` to restore v1.4.x behavior for all allow-list fields loaded from that file: ```json { "version": 1, "providers": { ... } } ``` | Value | Behavior | |---|---| | `2` (default, omitted) | v1.5.0 semantics — empty = deny all, `["*"]` = allow all | | `1` | v1.4.x semantics — empty = allow all | **What `version: 1` normalizes at startup** (before any other processing): | Field | Without `version: 1` | With `version: 1` | |---|---|---| | Provider key `models: []` | Deny all models | Allow all models (→ `["*"]`) | | VK `provider_configs: []` | No providers allowed | All configured providers added with `allowed_models: ["*"]` | | VK provider config `allowed_models: []` | Deny all models | Allow all models (→ `["*"]`) | | VK provider config `key_ids: []` | No keys allowed | All keys allowed (→ `key_ids: ["*"]`) | | VK `mcp_configs: []` | No MCP tools allowed | All configured MCP clients added with `tools_to_execute: ["*"]` | `version: 1` only applies to configuration loaded from `config.json`. Virtual Keys created or updated via the REST API always use v1.5.0 semantics regardless of this setting. The automatic database migration that runs on startup is also unaffected. `version: 1` is a temporary compatibility shim. Plan to migrate your `config.json` to explicit `["*"]` wildcards and remove the `version` field before the next major release. --- ## Complete Migration Checklist Make a copy of your config store database before starting the upgrade. Replace `"models": []` or missing `models` fields with `"models": ["*"]` on every provider key. Add `"allowed_models": ["*"]` and `"key_ids": ["*"]` to every `provider_configs` entry (or list specific values). Rename any `allowed_keys` fields to `key_ids`. Any Virtual Key with `"provider_configs": []` or no `provider_configs` will block all traffic. Replace `"tools_to_execute": []` with `"tools_to_execute": ["*"]`. Ensure every VK that needs MCP access has at least one `mcp_configs` entry. Update any client code that processes `weight` to accept `null` in addition to numbers. Ensure no list mixes `"*"` with specific values (e.g., `["*", "gpt-4o"]`) and no list has duplicate entries. Stop sending `keys` in provider create/update payloads and stop reading `keys` from provider responses. Use `/api/providers/{provider}/keys` for all key operations. Replace `enable_litellm_fallbacks` with the appropriate combination of `convert_text_to_chat`, `convert_chat_to_responses`, and `should_drop_params`. Move deployment mappings from provider-specific `deployments` fields into the top-level `aliases` field on each key. For Replicate, set `use_deployments_endpoint: true` if you were using the deployments endpoint. Replace `ExtraFields.ModelRequested` with `ExtraFields.OriginalModelRequested` (and optionally read `ExtraFields.ResolvedModelUsed`). Update JSON consumers reading `"model_requested"` to use `"original_model_requested"` and `"resolved_model_used"`. Replace `.Model` with `.RequestedModel` (and optionally `.ResolvedModel`) on any `StreamAccumulatorResult` usage. If your code reads `selected_key_id` / `selected_key_name` from the request context or log entries to attribute failed requests, add a null/empty check and fall back to `attempt_trail` for the full per-attempt key history. --- ## Troubleshooting **All requests returning 403/blocked after upgrade** A provider key has `models: []`, a Virtual Key has no `provider_configs`, or a provider config has `allowed_models: []`. Check Bifrost logs — a blocked request logs which rule denied it. Fix: add `"models": ["*"]` on provider keys, `"allowed_models": ["*"]` on VK provider configs. **MCP tools not being injected / tool calls blocked** The VK needs an `mcp_configs` entry for the MCP client with `"tools_to_execute": ["*"]` (or specific tools). **API returning 400 on VK create/update** A whitelist validation failure — either mixing `"*"` with specific values, or duplicate values in a list. **"No keys available" or key selection errors** A provider config with `key_ids` omitted or `[]` now blocks all keys (`allow_all_keys: false`). Add `"key_ids": ["*"]`. **Provider create/update errors about `keys` field** The `keys` field has been removed. Remove it from provider payloads and use `/api/providers/{provider}/keys` instead. **Replicate requests failing after upgrade** If you used `replicate_key_config.deployments`, move the mappings to the top-level `aliases` field and set `use_deployments_endpoint: true` if you were targeting the Deployments API. **Go SDK compilation errors on `ModelRequested` or `StreamAccumulatorResult.Model`** Rename to `OriginalModelRequested`/`ResolvedModelUsed` on ExtraFields, and `RequestedModel`/`ResolvedModel` on StreamAccumulatorResult.