Bifrost HTTP Transport Release v1.5.0-prerelease1
- feat: dedicated provider keys API — keys are now managed via
/api/providers/{provider}/keysendpoints instead of being embedded in provider create/update payloads - feat: VK provider config key_ids now supports ["*"] wildcard to allow all keys; empty key_ids denies all; handler resolves wildcard to AllowAllKeys flag without DB key lookups
- feat: now plugins can start injecting logs at trace level. Just use
ctx.Log(schemas.LogLevelInfo, "Test log") - feat: add option to disable automatic MCP tool injection per request
- feat: virtual key MCP configs now act as an execution-time allow-list — tools not permitted by the VK are blocked at inference and MCP tool execution
- refactor: standardize empty array conventions in bifrost. Empty array means no tools/keys are allowed, ["*"] means all tools/keys are allowed.
- feat: add support for request level extra headers in MCP tool execution.
- fix: add support for
x-bf-mcp-include-clientsandx-bf-mcp-include-toolsrequest headers to filter MCP tools/list response when using bifrost as an MCP gateway. - refactor: parallelize model listing for providers to speed up startup time.
- fix: send back accumulated usage in MCP agent mode.
- feat: MCP configuration now supports assigning virtual keys with per-tool access control.
- feat: adds option to allow MCP clients to run on all virtual keys without explicit assignment.
- feat: add support for pricing overrides.
- feat: add StabilityAI provider support to Bedrock.
- fix: handle text, vtt, srt response formats in OpenAI transcription response.
What Changed (The Short Version)
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 (deny by default) |
["*"] (wildcard)
| Not applicable | ✅ Allow all |
["a", "b"]
| Only a and b
| Only a and b (unchanged)
|
The old behavior was "allow all unless restricted." The new behavior is "deny all unless explicitly permitted."
This affects:
- Provider key
modelsfield — which models a key can serve - Virtual Key
provider_configs[].allowed_models— which models a VK can use per provider - Virtual Key
provider_configs[].key_ids— which API keys a VK can use (also renamed fromallowed_keys) - Virtual Key
mcp_configs[].tools_to_execute— which MCP tools a VK can execute - Virtual Key
provider_configsitself — which providers a VK can access
There are also two additional structural changes:
allowed_keysfield renamed tokey_idsin VK provider configsweightfield is now optional (nullable) on VK provider configs
Automatic Migration for Existing Data
**If you are running Bifrost with a database** (SQLite or Postgres), all existing data is automatically migrated on startup. You do not need to manually update your database records.The following automatic migrations run on upgrade:
- All provider keys with
models: []are converted tomodels: ["*"] - All virtual key provider configs with
allowed_models: []are converted toallowed_models: ["*"] - All virtual keys with no
provider_configsget backfilled with all currently configured providers (withallowed_models: ["*"]andkey_ids: ["*"]) - All virtual keys with no
mcp_configsget backfilled with all currently connected MCP clients (withtools_to_execute: ["*"])
The automatic migration only protects your existing data. If you also define your configuration through config.json or manage virtual keys via the API, you must update those manually using this guide.
Breaking Change 1: Provider Key models Field
Who is affected: Anyone who configures provider keys in config.json or programmatically with the field models absent or set to [].
What changed
The models field on a provider key previously defaulted to "allow all" when empty. It now means "allow none." You must explicitly use ["*"] to allow a key to serve all models.
Before (v1.4.x):
{
"providers": {
"openai": {
"keys": [
{
"id": "key-openai-1",
"value": "env.OPENAI_API_KEY",
"models": []
}
]
}
}
}models: [] → key served all models
After (v1.5.0):
{
"providers": {
"openai": {
"keys": [
{
"id": "key-openai-1",
"value": "env.OPENAI_API_KEY",
"models": ["*"]
}
]
}
}
}models: ["*"] → key serves all models
If you already specify explicit models, no change is needed:
{
"id": "key-openai-gpt4-only",
"value": "env.OPENAI_API_KEY",
"models": ["gpt-4o", "gpt-4o-mini"]
}This behaves the same in both versions — only the listed models are served.
How to update
Search your config.json for any provider key that has "models": [] or no models field at all, and add "models": ["*"] to restore the "allow all" behavior.
Breaking Change 2: Virtual Key allowed_models Field
Who is affected: Anyone who creates or updates Virtual Keys via config.json, the REST API, or the SDK with allowed_models absent or set to [].
What changed
The allowed_models field on a Virtual Key provider config previously defaulted to "allow all models for this provider" when empty. It now means "block all models from this provider." You must use ["*"] to allow all models.
Before (v1.4.x):
{
"governance": {
"virtual_keys": [
{
"id": "vk-my-app",
"provider_configs": [
{
"provider": "openai",
"weight": 1.0
}
]
}
]
}
}Missing allowed_models → all OpenAI models allowed
After (v1.5.0):
{
"governance": {
"virtual_keys": [
{
"id": "vk-my-app",
"provider_configs": [
{
"provider": "openai",
"allowed_models": ["*"],
"key_ids": ["*"],
"weight": 1.0
}
]
}
]
}
}allowed_models: ["*"] → all OpenAI models allowed
Before (v1.4.x):
curl -X POST http://localhost:8080/api/governance/virtual-keys \
-H "Content-Type: application/json" \
-d '{
"name": "my-app-key",
"provider_configs": [
{
"provider": "openai",
"weight": 1.0
}
]
}'Missing allowed_models → all OpenAI models allowed
After (v1.5.0):
curl -X POST http://localhost:8080/api/governance/virtual-keys \
-H "Content-Type: application/json" \
-d '{
"name": "my-app-key",
"provider_configs": [
{
"provider": "openai",
"allowed_models": ["*"],
"key_ids": ["*"],
"weight": 1.0
}
]
}'Breaking Change 3: Virtual Key Provider Configs — Deny-by-Default
Who is affected: Anyone creating Virtual Keys with no provider_configs array (or an empty one), expecting those keys to have unrestricted provider access.
What changed
In v1.4.x, a Virtual Key with no provider_configs had access to all configured providers. In v1.5.0, a Virtual Key with no provider_configs blocks all providers by default.
Before (v1.4.x): Virtual Key with provider_configs: [] → access to all providers
After (v1.5.0): Virtual Key with provider_configs: [] → no provider access (all blocked)
How to update
Every Virtual Key must now explicitly list the providers it is permitted to use. To allow access to all providers, add a config entry per provider with "allowed_models": ["*"].
{
"governance": {
"virtual_keys": [
{
"id": "vk-unrestricted",
"provider_configs": [
{ "provider": "openai", "allowed_models": ["*"], "key_ids": ["*"], "weight": 1.0 },
{ "provider": "anthropic", "allowed_models": ["*"], "key_ids": ["*"], "weight": 1.0 },
{ "provider": "azure", "allowed_models": ["*"], "key_ids": ["*"], "weight": 1.0 }
]
}
]
}
}Breaking Change 4: allowed_keys Renamed to key_ids
Who is affected: Anyone using the allowed_keys field in Virtual Key provider configs in config.json or the REST API.
What changed
The field used to restrict which provider API keys a Virtual Key can use has been renamed from allowed_keys to key_ids. The semantics follow the same deny-by-default model as all other v1.5.0 whitelist fields:
| Value | v1.4.x behavior | v1.5.0 behavior |
|---|---|---|
Field absent / []
| Allow all keys | Deny all keys |
["*"]
| Not applicable | Allow all keys (explicit wildcard) |
["key-1"]
| Only key-1 | Only key-1 (unchanged) |
Before (v1.4.x): allowed_keys omitted or [] → all keys allowed
{
"provider_configs": [
{
"provider": "openai",
"weight": 1.0
}
]
}After (v1.5.0): must use ["*"] explicitly — omitting key_ids or leaving it [] now blocks all keys
{
"provider_configs": [
{
"provider": "openai",
"key_ids": ["*"],
"allowed_models": ["*"],
"weight": 1.0
}
]
}Before (v1.4.x):
{
"provider_configs": [
{
"provider": "openai",
"allowed_keys": ["key-prod-001"],
"weight": 1.0
}
]
}After (v1.5.0): rename the field, no value change needed
{
"provider_configs": [
{
"provider": "openai",
"key_ids": ["key-prod-001"],
"allowed_models": ["*"],
"weight": 1.0
}
]
}Before (v1.4.x):
curl -X PUT http://localhost:8080/api/governance/virtual-keys/{vk_id} \
-d '{
"provider_configs": [
{
"provider": "openai",
"allowed_keys": ["key-prod-001"],
"weight": 1.0
}
]
}'After (v1.5.0):
curl -X PUT http://localhost:8080/api/governance/virtual-keys/{vk_id} \
-d '{
"provider_configs": [
{
"provider": "openai",
"key_ids": ["key-prod-001"],
"allowed_models": ["*"],
"weight": 1.0
}
]
}'Breaking Change 5: Virtual Key MCP tools_to_execute Field
Who is affected: Anyone configuring MCP tool filtering on Virtual Keys in config.json or the REST API.
What changed
The tools_to_execute field on a Virtual Key MCP config previously defaulted to "allow all tools" when empty. It now means "block all tools from this client." You must use ["*"] to allow all tools.
Additionally, the Virtual Key mcp_configs list itself now acts as a strict allow-list:
- No
mcp_configs→ all MCP tools blocked for this VK mcp_configswith entries → only the listed clients and tools are accessible
Before (v1.4.x):
{
"mcp_configs": [
{
"mcp_client_name": "my-tools-server",
"tools_to_execute": []
}
]
}Empty tools_to_execute → all tools from my-tools-server allowed
After (v1.5.0):
{
"mcp_configs": [
{
"mcp_client_name": "my-tools-server",
"tools_to_execute": ["*"]
}
]
}["*"] → all tools from my-tools-server allowed
Before (v1.4.x):
curl -X PUT http://localhost:8080/api/governance/virtual-keys/{vk_id} \
-d '{
"mcp_configs": [
{ "mcp_client_name": "billing-client", "tools_to_execute": [] }
]
}'After (v1.5.0):
curl -X PUT http://localhost:8080/api/governance/virtual-keys/{vk_id} \
-d '{
"mcp_configs": [
{ "mcp_client_name": "billing-client", "tools_to_execute": ["*"] }
]
}'MCP tool filtering semantics at a glance:
mcp_configs
| tools_to_execute
| Result |
|---|---|---|
| Not configured | N/A | All MCP tools blocked |
[{ client: "X" }]
| []
| All tools from X blocked |
[{ client: "X" }]
| ["*"]
| All tools from X allowed |
[{ client: "X" }]
| ["tool-a"]
| Only tool-a from X allowed
|
[{ client: "X" }, { client: "Y" }]
| ["*"] / ["*"]
| All tools from X and Y allowed; all other clients blocked |
Breaking Change 6: weight Field is Now Optional
Who is affected: Anyone programmatically processing or constructing Virtual Key provider config objects via the API.
What changed
The weight field on a Virtual Key provider config was previously a required float64. It is now an optional nullable value (*float64). The new semantics:
weight: 0.5— provider participates in weighted load balancingweight: nullor omitted — provider is configured and accessible but is excluded from weighted routing (it can still be used for directprovider/modelrequests or fallbacks)
This allows you to configure a provider on a VK for fallback purposes without including it in the normal weighted selection pool.
API response change: The weight field may now be null in API responses. Update any client code that assumes weight is always a number.
{
"provider_configs": [
{
"provider": "openai",
"allowed_models": ["*"],
"weight": 0.8
},
{
"provider": "anthropic",
"allowed_models": ["*"],
"weight": null
}
]
}In this example: 100% of weighted traffic goes to OpenAI. Anthropic is still reachable via anthropic/claude-3-5-sonnet-20241022 direct routing or as a manual fallback, but won't receive any traffic from weighted load balancing.
New Validation: WhiteList Rules
v1.5.0 introduces the WhiteList type, which enforces two new validation rules on all allow-list fields. The API now returns HTTP 400 if either rule is violated.
Rule 1: Wildcard cannot be mixed with other values
["*"] is only valid when it is the sole element. Using it alongside specific values is a validation error.
// ❌ Invalid — rejected with 400
{ "allowed_models": ["*", "gpt-4o"] }
// ✅ Valid
{ "allowed_models": ["*"] }
// ✅ Valid
{ "allowed_models": ["gpt-4o", "gpt-4o-mini"] }Rule 2: No duplicate values
// ❌ Invalid — rejected with 400
{ "allowed_models": ["gpt-4o", "gpt-4o"] }
// ✅ Valid
{ "allowed_models": ["gpt-4o", "gpt-4o-mini"] }These rules apply to: allowed_models, key_ids, models (on provider keys), tools_to_execute, tools_to_auto_execute, and allowed_extra_headers.
Complete Before/After Reference
Provider Key (config.json)
Before:
{
"providers": {
"openai": {
"keys": [
{ "id": "key-1", "value": "env.OPENAI_API_KEY", "weight": 1 }
]
}
}
}After:
{
"providers": {
"openai": {
"keys": [
{ "id": "key-1", "value": "env.OPENAI_API_KEY", "weight": 1, "models": ["*"] }
]
}
}
}Simple Virtual Key (config.json)
Before:
{
"governance": {
"virtual_keys": [
{
"id": "vk-simple",
"provider_configs": [
{ "provider": "openai", "weight": 1.0 }
]
}
]
}
}After:
{
"governance": {
"virtual_keys": [
{
"id": "vk-simple",
"provider_configs": [
{ "provider": "openai", "allowed_models": ["*"], "key_ids": ["*"], "weight": 1.0 }
]
}
]
}
}Virtual Key with Key Restrictions (config.json)
Before:
{
"provider_configs": [
{
"provider": "openai",
"allowed_keys": ["key-prod-001"],
"weight": 0.5
}
]
}After:
{
"provider_configs": [
{
"provider": "openai",
"key_ids": ["key-prod-001"],
"allowed_models": ["*"],
"weight": 0.5
}
]
}Virtual Key with MCP Tools (config.json)
Before:
{
"mcp_configs": [
{ "mcp_client_name": "tools-server", "tools_to_execute": [] }
]
}After:
{
"mcp_configs": [
{ "mcp_client_name": "tools-server", "tools_to_execute": ["*"] }
]
}Full Virtual Key (REST API)
Before:
curl -X POST http://localhost:8080/api/governance/virtual-keys \
-H "Content-Type: application/json" \
-d '{
"name": "prod-key",
"provider_configs": [
{
"provider": "openai",
"allowed_keys": ["key-prod-001"],
"weight": 0.7
},
{
"provider": "anthropic",
"weight": 0.3
}
],
"mcp_configs": [
{ "mcp_client_name": "my-mcp", "tools_to_execute": [] }
]
}'After:
curl -X POST http://localhost:8080/api/governance/virtual-keys \
-H "Content-Type: application/json" \
-d '{
"name": "prod-key",
"provider_configs": [
{
"provider": "openai",
"key_ids": ["key-prod-001"],
"allowed_models": ["*"],
"weight": 0.7
},
{
"provider": "anthropic",
"key_ids": ["*"],
"allowed_models": ["*"],
"weight": 0.3
}
],
"mcp_configs": [
{ "mcp_client_name": "my-mcp", "tools_to_execute": ["*"] }
]
}'Breaking Change 7: Compact plugin supports two new modes
Who is affected: Anyone using the compact plugin.
What changed
The compact plugin now supports two new modes:
- Chat to responses fallback: If Chat completion API is hit for models that only support Responses API, the chat completion is routed via the responses API.
- OpenAI Compatible parameters dropping: If a model does not support any standard OpenAI compatible model parameter, it is dropped.
The option enable_litellm_fallbacks is removed and replaced with:
compat.convert_text_to_chat: Enable text completion to chat completion fallback (original behavior)compat.convert_chat_to_responses: Enable chat completion to responses fallbackcompat.should_drop_params: Enable OpenAI Compatible parameters dropping
The following fields are removed or added to the response:
extra_fields.litellm_compatis removedextra_fields.dropped_compat_plugin_paramsreturns which parameters were dropped by this pluginextra_fields.converted_request_typereturns the type of convert to (NOTE: If the request is a streaming request, this will still be base request type. For streaming chat completions, converted_request_type will be "chat_completions" and not "chat_completion_stream")
Breaking Change 8: Image Edits No Longer Supported on Replicate's Image Generation Endpoint
Who is affected: Anyone using the Replicate provider to perform image editing operations via the /v1/images/generations endpoint (i.e., passing a source image or mask to generate a modified image).
What changed
The /v1/images/generations endpoint on the Replicate provider previously accepted image editing parameters (source image + optional mask) alongside a generation prompt. This behavior is no longer supported — the endpoint now only handles pure image generation (text-to-image).
Attempting to pass image editing parameters to /v1/images/generations on Replicate will return an error.
How to update
If you were using Replicate for image editing via the generations endpoint, you have the following option:
- Switch to the image edit endpoint
/v1/images/edits.
Breaking Change 9: Provider Keys API Separated from Provider API
Who is affected: Anyone who reads keys from provider API responses, or sends keys in provider create/update requests via the REST API.
What changed
Provider key management has been separated into dedicated endpoints. The keys field has been removed from all provider API requests and responses.
GET /api/providersandGET /api/providers/{provider}no longer return akeysfield.POST /api/providersno longer accepts akeysfield. Create the provider first, then add keys separately.PUT /api/providers/{provider}no longer accepts akeysfield. Existing keys are preserved as-is during provider updates.- The existing
GET /api/keysendpoint (flat key list across all providers) is unchanged.
New endpoints
| Method | Endpoint | Description |
|---|---|---|
GET
| /api/providers/{provider}/keys
| List all keys for a provider |
GET
| /api/providers/{provider}/keys/{key_id}
| Get a single key |
POST
| /api/providers/{provider}/keys
| Create a new key |
PUT
| /api/providers/{provider}/keys/{key_id}
| Update a key |
DELETE
| /api/providers/{provider}/keys/{key_id}
| Delete a key |
List keys returns { "keys": [...], "total": N }. All other endpoints return a single key object. Delete returns the deleted key for confirmation. Key values are always redacted in responses.
Create key notes:
idis auto-generated if omittedenableddefaults totrueif omittedvalueis required and must not be empty- Provider-specific config fields (
azure_key_config,vertex_key_config,bedrock_key_config,vllm_key_config) are supported as before - Keyless providers (e.g., Ollama) reject key creation with
400
Update key notes:
Send the full key object. Redacted values sent back unchanged are automatically preserved (same merge behavior as before).
How to update
Creating a provider with keys:
Before (v1.4.x):
curl -X POST localhost:8080/api/providers -d '{
"provider": "openai",
"keys": [{"name": "main", "value": "sk-..."}],
"network_config": { ... }
}'After (v1.5.0): Create the provider first, then add keys separately:
curl -X POST localhost:8080/api/providers -d '{
"provider": "openai",
"network_config": { ... }
}'
curl -X POST localhost:8080/api/providers/openai/keys -d '{
"name": "main", "value": "sk-..."
}'Reading keys:
Before (v1.4.x):
curl localhost:8080/api/providers/openai | jq '.keys'After (v1.5.0):
curl localhost:8080/api/providers/openai/keys | jq '.keys'Updating / deleting keys:
Before (v1.4.x): Bulk replace via provider update:
curl -X PUT localhost:8080/api/providers/openai -d '{
"keys": [{"id": "key-1", "name": "updated", "value": "sk-new"}],
"network_config": { ... }
}'After (v1.5.0): Individual key operations:
# Update one key
curl -X PUT localhost:8080/api/providers/openai/keys/key-1 \
-d '{ "name": "updated", "value": "sk-new" }'
# Delete a key
curl -X DELETE localhost:8080/api/providers/openai/keys/key-2
# Add a new key
curl -X POST localhost:8080/api/providers/openai/keys \
-d '{ "name": "new-key", "value": "sk-..." }'Other behavioral changes
- Model discovery is automatically triggered after key create, update, and delete operations.
- Provider updates (
PUT /api/providers/{provider}) now invalidate key status caches, so key statuses refresh after provider config changes. - Existing keys are safe — no data migration is needed. All existing keys remain intact and accessible via the new endpoints.
Quick Migration Checklist
Use this checklist when upgrading to v1.5.0:
Find every provider key in your `config.json` that has `"models": []` or no `models` field. Add `"models": ["*"]` to each. Find every `provider_configs` entry in your Virtual Keys (in `config.json` and any automation that calls the API). Add `"allowed_models": ["*"]` if you want all models, or list specific models you want to allow. Any Virtual Key with `"provider_configs": []` or no `provider_configs` will block all traffic. Add the providers you want to allow. Search your `config.json` and API calls for `"allowed_keys"` and rename it to `"key_ids"`. Then: - If `allowed_keys` was omitted or `[]` (previously meaning "allow all"), change to `"key_ids": ["*"]` — an empty or omitted `key_ids` now **blocks all keys**. - If `allowed_keys` listed specific key IDs, keep the same values — only the field name changes. Change any `"tools_to_execute": []` to `"tools_to_execute": ["*"]` if you want to allow all tools. Ensure every VK that needs MCP access has at least one `mcp_configs` entry. If you parse the Virtual Key API response in your code, update the `weight` field handler to accept `null` values in addition to numbers. Check that none of your lists mix `"*"` with other values (e.g., `["*", "gpt-4o"]`), and that none have duplicate entries. These will now be rejected with HTTP 400. Stop sending `keys` in provider create/update payloads and stop reading `keys` from provider responses. Use the new `/api/providers/{provider}/keys` endpoints for all key CRUD operations.Troubleshooting
All requests are returning 403/blocked after upgrade
This usually means a provider key has models: [] or a Virtual Key has no provider_configs, or a provider config has allowed_models: []. Check the Bifrost logs — a blocked request will log which rule denied it.
Fix: Follow the checklist above. Ensure models: ["*"] on provider keys and allowed_models: ["*"] on VK provider configs.
MCP tools are not being injected / tool calls are blocked
The VK needs an mcp_configs entry for the MCP client, and that entry needs "tools_to_execute": ["*"] (or a specific tool list).
Fix: Add or update the mcp_configs on the Virtual Key.
API returning 400 on virtual key create/update
The most common cause is a whitelist validation failure — either ["*", "gpt-4o"] mixing a wildcard with a specific value, or a duplicate value in a list.
Fix: Use only ["*"] alone or a list of specific values without duplicates.
Requests fail with "no keys available" or key selection errors after upgrade
A provider config with key_ids omitted or set to [] now blocks all keys (allow_all_keys: false, no specific keys configured). This is different from allowed_models — there is no automatic migration for key_ids.
Fix: Add "key_ids": ["*"] to any provider config that previously had allowed_keys: [] or no allowed_keys field.
Provider create/update returns errors about keys field
The keys field has been removed from provider API payloads. Use the dedicated /api/providers/{provider}/keys endpoints to manage keys separately.
Fix: Remove keys from your provider create/update requests. Create keys via POST /api/providers/{provider}/keys after creating the provider.
Existing keys work fine but newly created keys are blocked
The automatic migration only updates existing data. New keys created after upgrade must follow the new semantics. Use ["*"] for all allow-list fields where you want unrestricted access.
Installation
Docker
docker run -p 8080:8080 maximhq/bifrost:v1.5.0-prerelease1Binary Download
npx @maximhq/bifrost --transport-version v1.5.0-prerelease1Docker Images
maximhq/bifrost:v1.5.0-prerelease1- This specific versionmaximhq/bifrost:latest- Latest version (updated with this release)
This release was automatically created with dependencies: core v1.5.0, framework v1.3.0. All plugins have been validated and updated.