> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getbifrost.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating to v1.5.0

> 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.

<Warning>
  **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.
</Warning>

***

## 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 theme={null}
{ "value": "env.OPENAI_API_KEY", "models": [] }
```

`models: []` → key served all models

**After:**

```json theme={null}
{ "value": "env.OPENAI_API_KEY", "models": ["*"] }
```

### Virtual Key `allowed_models`

**Before:**

```json theme={null}
{ "provider": "openai", "weight": 1.0 }
```

Missing `allowed_models` → all models allowed

**After:**

```json theme={null}
{ "provider": "openai", "allowed_models": ["*"], "key_ids": ["*"], "weight": 1.0 }
```

### Virtual Key MCP `tools_to_execute`

**Before:**

```json theme={null}
{ "mcp_client_name": "my-tools", "tools_to_execute": [] }
```

**After:**

```json theme={null}
{ "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.

<Note>
  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.
</Note>

**Before:**

```json theme={null}
{ "provider": "openai", "allowed_keys": ["key-prod-001"], "weight": 1.0 }
```

**After:**

```json theme={null}
{ "provider": "openai", "key_ids": ["key-prod-001"], "allowed_models": ["*"], "weight": 1.0 }
```

To allow all keys:

```json theme={null}
{ "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 theme={null}
{
  "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 theme={null}
// ❌ Invalid
{ "allowed_models": ["*", "gpt-4o"] }

// ✅ Valid
{ "allowed_models": ["*"] }
```

**Rule 2: No duplicate values**

```json theme={null}
// ❌ 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: Virtual Key `budget` Changed to Multi-Budget `budgets`

The budget model on Virtual Keys and provider configs has been restructured from a single budget to support multiple budgets with different reset intervals.

In v1.4.x, a Virtual Key had a single `budget_id` foreign key pointing to one budget, and each provider config also had a single `budget_id`. In v1.5.0, the association is inverted: budgets now reference their parent via `virtual_key_id` or `provider_config_id`, and both Virtual Keys and provider configs support an array of budgets.

The database migration runs automatically on startup, converting existing single-budget associations into the new multi-budget structure.

### API request changes

**Creating a Virtual Key with a budget:**

**Before:**

```json theme={null}
{
  "name": "my-key",
  "budget": {
    "max_limit": 100.0,
    "reset_duration": "1d"
  }
}
```

**After:**

```json theme={null}
{
  "name": "my-key",
  "budgets": [
    { "max_limit": 100.0, "reset_duration": "1d" },
    { "max_limit": 500.0, "reset_duration": "1M" }
  ]
}
```

**Provider config budgets:**

**Before:**

```json theme={null}
{
  "provider": "openai",
  "budget": { "max_limit": 50.0, "reset_duration": "1h" }
}
```

**After:**

```json theme={null}
{
  "provider": "openai",
  "budgets": [
    { "max_limit": 50.0, "reset_duration": "1h" }
  ]
}
```

### API response changes

| Field                       | v1.4.x        | v1.5.0                                    |
| --------------------------- | ------------- | ----------------------------------------- |
| Virtual Key `budget_id`     | Present       | **Removed**                               |
| Virtual Key `budget`        | Single object | **Removed** - replaced by `budgets` array |
| Virtual Key `budgets`       | Not present   | Array of budget objects                   |
| Provider config `budget_id` | Present       | **Removed**                               |
| Provider config `budget`    | Single object | **Removed** - replaced by `budgets` array |
| Provider config `budgets`   | Not present   | Array of budget objects                   |

<Note>
  `calendar_aligned` has moved from the budget level to the Virtual Key level. It now applies uniformly to all budgets under a VK.
</Note>

### Viewing budget for a given key

Budgets are returned as part of the Virtual Key response on all admin endpoints:

* `GET /api/governance/virtual-keys` - list all VKs (includes `budgets` on each VK and each provider config)
* `GET /api/governance/virtual-keys/{id}` - get a single VK with full budget details
* `GET /api/governance/virtual-keys/quota` - self-service endpoint (authenticate with the VK value via `x-bf-vk` header)

***

## Breaking Change 7: 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 theme={null}
curl -X POST localhost:8080/api/providers -d '{
  "provider": "openai",
  "keys": [{"name": "main", "value": "sk-..."}]
}'
```

**After:** Create provider first, then add keys:

```bash theme={null}
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 theme={null}
curl -X PUT localhost:8080/api/providers/openai -d '{"keys": [{"id": "key-1", "value": "sk-new"}]}'
```

**After:** Individual key operations:

```bash theme={null}
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 8: Compat Plugin Restructured

The `enable_litellm_fallbacks` option has been removed and replaced with three granular options.

**Before:**

```json theme={null}
{ "compat": { "enable_litellm_fallbacks": true } }
```

**After:**

```json theme={null}
{
  "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 9: 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`.

<Note>
  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.
</Note>

***

## Breaking Change 10: 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 theme={null}
{
  "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 theme={null}
{
  "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

<Warning>
  `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.
</Warning>

**Before:**

```json theme={null}
{
  "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 theme={null}
{
  "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 theme={null}
{
  "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 theme={null}
{
  "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 theme={null}
{
  "replicate_key_config": {
    "deployments": {
      "my-model": "owner/model-name/version-hash"
    }
  }
}
```

**After:**

```json theme={null}
{
  "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 11: Go SDK - `ExtraFields` Model Fields Renamed

`ModelRequested string` has been replaced by two fields on `BifrostResponseExtraFields` and `BifrostErrorExtraFields`.

**Before:**

```go theme={null}
model := response.ExtraFields.ModelRequested
```

**After:**

```go theme={null}
// 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 12: Go SDK - `StreamAccumulatorResult` Field Renamed

`Model string` has been replaced by two fields on `StreamAccumulatorResult` (returned by tracer streaming accumulation methods).

**Before:**

```go theme={null}
result.Model
```

**After:**

```go theme={null}
result.RequestedModel // original alias from the caller
result.ResolvedModel  // actual model identifier used by the provider
```

***

## Breaking Change 13: `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 theme={null}
keyID, _ := ctx.Value(schemas.BifrostContextKeySelectedKeyID).(string)
// keyID was always populated, even on error
```

**After:**

```go theme={null}
// 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.

<Note>
  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.
</Note>

***

## Breaking Change 14: Direct Key Bypass Removed (HTTP Gateway and Go SDK)

The "Direct Key Bypass" feature has been **removed entirely** in v1.5.0, on both surfaces:

* **HTTP gateway:** the `allow_direct_keys` config flag and the header pass-through (`Authorization`, `x-api-key`, `x-goog-api-key`, plus the Bedrock `x-bf-bedrock-*` and Azure `x-bf-azure-endpoint` integration paths) no longer forward keys to upstream providers.
* **Go SDK:** the `schemas.BifrostContextKeyDirectKey` context value has been deleted, along with the documented "Direct Key (Go SDK Only)" API.

**All requests must now resolve to a Bifrost-managed provider key**, either implicitly (the configured key pool plus weighted selection) or by pinning a registered key via `BifrostContextKeyAPIKeyID` / `BifrostContextKeyAPIKeyName` (Go SDK) or virtual keys (`sk-bf-*`, HTTP).

### What changed

| Field / surface                                                                                                                       | Status                                                                                                         |
| ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `client.allow_direct_keys` in `config.json`                                                                                           | **Removed** — field is no longer recognized; ignored if present                                                |
| `client_config.allow_direct_keys` over `PUT /api/config`                                                                              | **Removed** — field is dropped from the request payload                                                        |
| Web UI **Settings → Security → "Allow Direct API Keys"** toggle                                                                       | **Removed**                                                                                                    |
| `Authorization: Bearer sk-...` header → upstream provider                                                                             | **No longer forwarded** as a direct key. Bearer values starting with `sk-bf-` continue to work as virtual keys |
| `x-api-key` / `x-goog-api-key` header → upstream provider                                                                             | **No longer forwarded** as a direct key                                                                        |
| `x-bf-bedrock-api-key` / `x-bf-bedrock-access-key` / `x-bf-bedrock-secret-key` / `x-bf-bedrock-session-token` / `x-bf-bedrock-region` | **No longer extracted** by the Bedrock integration                                                             |
| `x-bf-azure-endpoint` + bare Authorization header on Azure OpenAI integration routes                                                  | **No longer extracted** as a direct Azure key                                                                  |
| Database column `config_client.allow_direct_keys`                                                                                     | **Auto-dropped** on first startup of v1.5.0                                                                    |
| `schemas.BifrostContextKeyDirectKey` (Go SDK constant)                                                                                | **Removed** — code referencing it will fail to compile                                                         |
| `Bifrost.getAllSupportedKeys` / `getKeysForBatchAndFileOps` / `selectKeyFromProviderForModelWithPool` DirectKey branches              | **Removed** — these no longer special-case a caller-supplied key                                               |

### How to update

**1. Remove the field from `config.json`:**

```diff theme={null}
{
  "client": {
    "enable_logging": true,
-   "allow_direct_keys": false,
    "allowed_origins": ["*"]
  }
}
```

The field is silently ignored, but leaving it in is misleading.

**2. Remove the field from any REST API integration that calls `PUT /api/config`:** the field has been dropped from the `client_config` schema.

**3. Migrate any HTTP caller that relied on header-passed keys:**

* **For per-tenant or per-user key isolation:** create a Bifrost virtual key per tenant (with budgets, rate limits, and provider/model allow-lists) and have callers send `Authorization: Bearer sk-bf-<tenant-vk>`.
* **For routing requests across multiple provider keys:** add the keys to Bifrost via `POST /api/providers/{provider}/keys` and let Bifrost handle weighted selection and rotation.
* **For Bedrock callers using AWS credentials in `x-bf-bedrock-*` headers:** add a Bedrock provider key with the same credentials (`access_key`, `secret_key`, `region`, optional `session_token`) via the providers API and reference it from your virtual key.
* **For Azure direct-key callers using `x-bf-azure-endpoint` + Authorization:** add the Azure deployment as a provider key with `azure_key_config.endpoint` and reference it from your virtual key.

**4. Migrate Go SDK callers off `BifrostContextKeyDirectKey`:**

The constant is gone. Replace any in-process injection with one of the following supported alternatives.

**Before (no longer compiles):**

```go theme={null}
ctx = context.WithValue(ctx, schemas.BifrostContextKeyDirectKey, schemas.Key{
    Value:  *schemas.NewEnvVar("sk-runtime-secret"),
    Models: []string{"gpt-4o"},
    Weight: 1.0,
})
```

**After — register the key with the account, then pin by ID or name:**

```go theme={null}
// In your schemas.Account implementation, return the key from GetKeysForProvider.
// Each registered key has a stable ID and Name; reference either one from the request context.

ctx = context.WithValue(ctx, schemas.BifrostContextKeyAPIKeyName, "runtime-secret")
// or
ctx = context.WithValue(ctx, schemas.BifrostContextKeyAPIKeyID, "<key-id-from-account>")
```

If your account is database-backed, add the key via `POST /api/providers/{provider}/keys`. If you maintain a custom in-memory `Account`, return the key from `GetKeysForProvider`. Both routes give you the same per-request pinning behaviour DirectKey provided, plus governance, rotation on retry, and per-key cost attribution.

**For providers that allow keyless requests** (ambient credentials, IAM roles, etc.), `BifrostContextKeySkipKeySelection` is unchanged.

### Why it was removed

The direct-key path bypassed Bifrost's key management entirely, which meant:

* **No governance** — virtual key budgets, rate limits, provider/model allow-lists, and routing rules were not applied to direct-key traffic
* **No rotation or fallback** — direct keys were used as-is with no retry across alternate keys
* **No observability attribution** — header-provided keys had a synthetic `key_id: "header-provided"` that defeated per-key cost and usage analytics
* **Security surface area** — a misconfigured HTTP deployment could leak provider credentials through logs or proxy chains; the Go SDK equivalent had the same hazard for any caller logging context contents

The supported `BifrostContextKeyAPIKeyID` / `BifrostContextKeyAPIKeyName` path covers the "pick a specific key per request" use case without the governance and observability gaps.

***

## 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 theme={null}
{
  "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: ["*"]` |

<Note>
  `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.
</Note>

<Warning>
  `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.
</Warning>

***

## Complete Migration Checklist

<Steps>
  <Step title="Backup your database">
    Make a copy of your config store database before starting the upgrade.
  </Step>

  <Step title="Update provider key models in config.json">
    Replace `"models": []` or missing `models` fields with `"models": ["*"]` on every provider key.
  </Step>

  <Step title="Add allowed_models and key_ids to every VK provider config">
    Add `"allowed_models": ["*"]` and `"key_ids": ["*"]` to every `provider_configs` entry (or list specific values). Rename any `allowed_keys` fields to `key_ids`.
  </Step>

  <Step title="Ensure every VK has at least one provider config">
    Any Virtual Key with `"provider_configs": []` or no `provider_configs` will block all traffic.
  </Step>

  <Step title="Update tools_to_execute for MCP configs">
    Replace `"tools_to_execute": []` with `"tools_to_execute": ["*"]`. Ensure every VK that needs MCP access has at least one `mcp_configs` entry.
  </Step>

  <Step title="Migrate budget to budgets on Virtual Keys">
    Update any API integration that creates or updates Virtual Keys or provider configs to use the `budgets` array instead of the singular `budget` object. Existing budgets are migrated automatically on startup.
  </Step>

  <Step title="Handle nullable weight in API consumers">
    Update any client code that processes `weight` to accept `null` in addition to numbers.
  </Step>

  <Step title="Fix invalid WhiteList values">
    Ensure no list mixes `"*"` with specific values (e.g., `["*", "gpt-4o"]`) and no list has duplicate entries.
  </Step>

  <Step title="Migrate key management to dedicated endpoints">
    Stop sending `keys` in provider create/update payloads and stop reading `keys` from provider responses. Use `/api/providers/{provider}/keys` for all key operations.
  </Step>

  <Step title="Update compat plugin config">
    Replace `enable_litellm_fallbacks` with the appropriate combination of `convert_text_to_chat`, `convert_chat_to_responses`, and `should_drop_params`.
  </Step>

  <Step title="Migrate provider deployments to aliases">
    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.
  </Step>

  <Step title="Update Go SDK references to ExtraFields.ModelRequested">
    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"`.
  </Step>

  <Step title="Update Go SDK references to StreamAccumulatorResult.Model">
    Replace `.Model` with `.RequestedModel` (and optionally `.ResolvedModel`) on any `StreamAccumulatorResult` usage.
  </Step>

  <Step title="Handle empty selected_key_id on terminal retry failures">
    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.
  </Step>

  <Step title="Migrate direct-key callers off both surfaces">
    Remove `allow_direct_keys` from `config.json` and any `PUT /api/config` payloads. Audit HTTP callers that sent provider keys in `Authorization` / `x-api-key` / `x-goog-api-key` / `x-bf-bedrock-*` / `x-bf-azure-endpoint` headers — those keys are no longer forwarded. Audit Go SDK callers for any reference to `schemas.BifrostContextKeyDirectKey` — the constant is removed and code referencing it will not compile. Replace both flavours with a Bifrost-managed provider key, optionally pinned per request via `BifrostContextKeyAPIKeyID` / `BifrostContextKeyAPIKeyName` (Go SDK) or a virtual key (`sk-bf-*`, HTTP).
  </Step>
</Steps>

***

## 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.

**Calls authenticating with raw provider keys started failing with auth errors**

The HTTP gateway no longer extracts provider keys from `Authorization` / `x-api-key` / `x-goog-api-key` headers (or `x-bf-bedrock-*` / `x-bf-azure-endpoint` on the Bedrock and Azure integrations). Either issue a virtual key (`sk-bf-*`) per caller and have them send that, or register the provider credentials as a Bifrost-managed key and route via virtual keys. See [Breaking Change 14](#breaking-change-14-direct-key-bypass-removed-http-gateway-and-go-sdk).

**Virtual Key response missing `budgets` field**

If you are creating Virtual Keys via the API using the old singular `budget` field, it will be ignored. Use the new `budgets` array format instead. If the VK was migrated from v1.4.x, the automatic migration should have converted the existing budget. Check that the migration ran successfully on startup.

**Go SDK build error: `undefined: schemas.BifrostContextKeyDirectKey`**

The constant is removed in v1.5.0. Register the key on your `Account` implementation (or via `POST /api/providers/{provider}/keys` if you use the database-backed config store) and pin it per request with `BifrostContextKeyAPIKeyID` or `BifrostContextKeyAPIKeyName`. See [Breaking Change 14](#breaking-change-14-direct-key-bypass-removed-http-gateway-and-go-sdk) for a before/after example.
