The governance block lets you seed all governance resources directly in config.json. On startup, Bifrost loads these into the configuration store. This is the recommended approach for GitOps workflows where governance state is managed as code.
Governance enforcement is always active in OSS — you do not need a plugin entry to enable it. To require a virtual key on every inference request, set client.enforce_auth_on_inference: true. This is the global default, but a more specific inference-auth flag such as governance.auth_config.disable_auth_on_inference overrides it; if no specific override is set, client.enforce_auth_on_inference applies.
Admin Authentication
Protect the Bifrost dashboard and management API with username/password auth:
{
"governance": {
"auth_config": {
"is_enabled": true,
"admin_username": "env.BIFROST_ADMIN_USERNAME",
"admin_password": "env.BIFROST_ADMIN_PASSWORD",
"disable_auth_on_inference": false
}
}
}
| Field | Default | Description |
|---|
is_enabled | false | Enable admin username/password auth |
admin_username | — | Admin username (supports env. prefix) |
admin_password | — | Admin password (supports env. prefix) |
disable_auth_on_inference | false | Skip auth check on /v1/* inference routes |
Virtual Keys
Virtual keys are issued to clients and act as scoped API tokens. Each key specifies which providers, models, and API keys the bearer is allowed to use.
{
"governance": {
"virtual_keys": [
{
"id": "vk-team-platform",
"name": "platform-team",
"value": "env.VK_PLATFORM_TEAM",
"is_active": true,
"provider_configs": [
{
"provider": "openai",
"allowed_models": ["gpt-4o", "gpt-4o-mini"],
"key_ids": ["*"],
"weight": 1
},
{
"provider": "anthropic",
"allowed_models": ["*"],
"key_ids": ["*"],
"weight": 1
}
]
}
]
}
}
Virtual Key Fields
| Field | Required | Description |
|---|
id | Yes | Unique virtual key ID (referenced by budgets / rate limits) |
name | Yes | Human-readable name |
value | No | The key token sent by clients (use env. prefix). Auto-generated if omitted |
is_active | No | Default true. Set false to disable without deleting |
team_id | No | Associate with a team (mutually exclusive with customer_id) |
customer_id | No | Associate with a customer |
rate_limit_id | No | Attach a rate limit |
calendar_aligned | No | Snap budget resets to day/week/month/year boundaries |
provider_configs | No | Allowed provider/model/key combinations (empty = deny all) |
Provider Config Fields
| Field | Required | Description |
|---|
provider | Yes | Provider name (e.g. "openai") |
allowed_models | No | Model allow-list. ["*"] = all models; [] = deny all |
key_ids | No | Provider key names allowed for this VK. ["*"] = all keys; [] = deny all. Use key name values (not UUIDs) in config.json |
weight | No | Load-balancing weight when multiple provider configs are present |
rate_limit_id | No | Attach a per-provider-config rate limit |
Budgets
Budgets cap cumulative spend (in USD) for a virtual key or provider config over a rolling window:
{
"governance": {
"budgets": [
{
"id": "budget-platform-monthly",
"max_limit": 500.00,
"reset_duration": "1M",
"virtual_key_id": "vk-team-platform"
}
]
}
}
| Field | Required | Description |
|---|
id | Yes | Unique budget ID |
max_limit | Yes | Maximum spend in USD |
reset_duration | Yes | Window length: "30s", "5m", "1h", "1d", "1w", "1M", "1Y" |
virtual_key_id | No | Attach to a virtual key (mutually exclusive with provider_config_id) |
provider_config_id | No | Attach to a provider config ID |
Rate Limits
Rate limits cap requests or tokens over a rolling window:
{
"governance": {
"rate_limits": [
{
"id": "rl-platform-hourly",
"request_max_limit": 1000,
"request_reset_duration": "1h",
"token_max_limit": 1000000,
"token_reset_duration": "1h"
}
]
}
}
| Field | Required | Description |
|---|
id | Yes | Unique rate limit ID |
request_max_limit | No | Maximum requests in window |
request_reset_duration | No | Window for request counter |
token_max_limit | No | Maximum tokens (input + output) in window |
token_reset_duration | No | Window for token counter |
Attach a rate limit to a virtual key via virtual_keys[].rate_limit_id, or to a provider config via virtual_keys[].provider_configs[].rate_limit_id.
Routing Rules
Routing rules dynamically select the provider and model for each request based on a CEL expression. They are evaluated in priority order before the request is dispatched.
{
"governance": {
"routing_rules": [
{
"id": "route-gpt4-to-azure",
"name": "Redirect GPT-4o to Azure",
"cel_expression": "request.model == 'gpt-4o'",
"targets": [
{ "provider": "azure", "model": "gpt-4o", "weight": 1.0 }
]
},
{
"id": "route-cost-split",
"name": "Split traffic 70/30 between providers",
"cel_expression": "true",
"targets": [
{ "provider": "openai", "weight": 0.7 },
{ "provider": "anthropic", "weight": 0.3 }
]
}
]
}
}
Rule Fields
| Field | Required | Description |
|---|
id | Yes | Unique rule ID |
name | Yes | Human-readable name |
cel_expression | No | CEL expression. "true" matches every request |
targets | Yes | Weighted target list (weights must sum to 1.0) |
enabled | No | Default true |
priority | No | Evaluation order within scope — lower numbers run first |
scope | No | "global" (default), "team", "customer", "virtual_key" |
scope_id | Conditional | Required when scope is not "global" |
chain_rule | No | If true, re-evaluates the chain after this rule matches |
fallbacks | No | Ordered fallback provider list if primary target fails |
Target Fields
| Field | Required | Description |
|---|
weight | Yes | Fraction of traffic (all weights in a rule must sum to 1.0) |
provider | No | Target provider. Omit to keep the incoming request’s provider |
model | No | Target model. Omit to keep the incoming request’s model |
key_id | No | Pin a specific API key by name |
Customers & Teams
Define organizational entities and attach budgets or rate limits to them:
{
"governance": {
"customers": [
{
"id": "customer-acme",
"name": "Acme Corp",
"budget_id": "budget-acme-monthly",
"rate_limit_id": "rl-acme-hourly"
}
],
"teams": [
{
"id": "team-ml",
"name": "ML Team",
"customer_id": "customer-acme",
"budget_id": "budget-team-ml"
}
]
}
}
Full Governance Example
{
"$schema": "https://www.getbifrost.ai/schema",
"encryption_key": "env.BIFROST_ENCRYPTION_KEY",
"client": {
"enforce_auth_on_inference": true
},
"governance": {
"auth_config": {
"is_enabled": true,
"admin_username": "env.BIFROST_ADMIN_USERNAME",
"admin_password": "env.BIFROST_ADMIN_PASSWORD"
},
"budgets": [
{
"id": "budget-platform",
"max_limit": 1000.00,
"reset_duration": "1M",
"virtual_key_id": "vk-platform"
}
],
"rate_limits": [
{
"id": "rl-platform",
"request_max_limit": 5000,
"request_reset_duration": "1h",
"token_max_limit": 5000000,
"token_reset_duration": "1h"
}
],
"virtual_keys": [
{
"id": "vk-platform",
"name": "platform-key",
"value": "env.VK_PLATFORM",
"is_active": true,
"rate_limit_id": "rl-platform",
"provider_configs": [
{
"provider": "openai",
"allowed_models": ["*"],
"key_ids": ["*"],
"weight": 1
}
]
}
],
"routing_rules": [
{
"id": "fallback-to-anthropic",
"name": "Fallback on error",
"cel_expression": "true",
"targets": [{ "provider": "openai", "weight": 1.0 }],
"fallbacks": ["anthropic"]
}
]
},
"providers": {
"openai": {
"keys": [{ "name": "openai-primary", "value": "env.OPENAI_API_KEY", "models": ["*"], "weight": 1.0 }]
},
"anthropic": {
"keys": [{ "name": "anthropic-primary", "value": "env.ANTHROPIC_API_KEY", "models": ["*"], "weight": 1.0 }]
}
},
"config_store": {
"enabled": true,
"type": "postgres",
"config": {
"host": "env.PG_HOST",
"port": "5432",
"user": "env.PG_USER",
"password": "env.PG_PASSWORD",
"db_name": "bifrost"
}
}
}