> ## Documentation Index
> Fetch the complete documentation index at: https://bifrost-dev.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Governance

> Seed virtual keys, budgets, rate limits, routing rules, and admin auth in config.json

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.

<Note>
  In default split mode, file-backed governance resources seed or update the DB by hash while unrelated DB-only resources are preserved. With `source_of_truth: "config.json"`, only governance sub-sections that are explicitly present in the file are authoritative. Omit a sub-section to leave DB-managed rows alone; set it to an empty array only when you intend to remove stored rows for that sub-section. See [Source of Truth & Reconciliation](/deployment-guides/config-json/source-of-truth).
</Note>

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

***

## Admin Authentication

Protect the Bifrost dashboard and management API with username/password auth:

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

```json theme={null}
{
  "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                                                                                                                                                                                                             |
| `allow_all_providers` | No       | Default `false`. When `true`, the key can use every provider, including ones added later. Listed `provider_configs` retain their per-provider model/key/budget/rate-limit rules. Providers without an entry get all models, all keys, and no per-provider limits |
| `provider_configs`    | No       | Allowed provider/model/key combinations (empty = deny all, unless `allow_all_providers` is `true`)                                                                                                                                                               |

### 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 an owning governance entity over a rolling window. The owner is declared on the budget:

```json theme={null}
{
  "governance": {
    "budgets": [
      {
        "id": "budget-team-ml-monthly",
        "max_limit": 500.00,
        "reset_duration": "1M",
        "team_id": "team-ml"
      }
    ]
  }
}
```

| 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"`, `"1Q"`, `"1Y"`                                                                                                                                                                              |
| `reset_config`       | No       | Quarterly windows only. `{ "quarter_start_month": 4 }` sets the first month of Q1. Values are integers from 1 through 12; omit the field for January. See [Quarterly budgets](/features/governance/budget-and-limits#quarterly-budgets-and-fiscal-quarters) |
| `team_id`            | No       | Attach to a team. Set this on the budget; teams do not have `budget_id`                                                                                                                                                                                     |
| `virtual_key_id`     | No       | Attach to a virtual key                                                                                                                                                                                                                                     |
| `provider_config_id` | No       | Attach to a provider config ID                                                                                                                                                                                                                              |
| `model_config_id`    | No       | Attach to a model config ID                                                                                                                                                                                                                                 |
| `customer_id`        | No       | Attach to a customer                                                                                                                                                                                                                                        |

Set at most one owner field on a budget. A team can own multiple budgets with different reset durations.

***

## Rate Limits

Rate limits cap requests or tokens over a rolling window:

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

***

## Model Limits

`governance.model_configs` applies budgets and rate limits keyed on a model name, an optional provider, and a scope. This is the same data the **Budget & Limits → Model Limits** UI manages.

```json theme={null}
{
  "governance": {
    "model_configs": [
      {
        "id": "mc-gpt4o-global",
        "model_name": "gpt-4o",
        "provider": "openai",
        "budget_id": "budget-production",
        "rate_limit_id": "rl-platform-hourly"
      },
      {
        "id": "mc-openai-provider",
        "model_name": "*",
        "provider": "openai",
        "scope": "global",
        "budget_id": "budget-production"
      },
      {
        "id": "mc-vk-dev-toplevel",
        "model_name": "*",
        "scope": "virtual_key",
        "scope_id": "vk-dev-all",
        "budget_id": "budget-dev"
      }
    ]
  }
}
```

| Field           | Required    | Description                                                     |
| --------------- | ----------- | --------------------------------------------------------------- |
| `id`            | Yes         | Unique identifier                                               |
| `model_name`    | Yes         | Model name, or `"*"` to match all models                        |
| `provider`      | No          | Provider name; omit to apply across all providers               |
| `scope`         | No          | `"global"` (default, all traffic) or `"virtual_key"` (one VK)   |
| `scope_id`      | Conditional | Required when `scope` is `"virtual_key"` — the virtual key `id` |
| `budget_id`     | No          | References a `governance.budgets` entry                         |
| `rate_limit_id` | No          | References a `governance.rate_limits` entry                     |

***

## Routing Rules

Routing rules dynamically select the provider and model for each request based on a [CEL](https://cel.dev) expression. They are evaluated in priority order before the request is dispatched.

```json theme={null}
{
  "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 rate limits directly. Team budgets reference their owner through `governance.budgets[].team_id`:

```json theme={null}
{
  "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"
      }
    ],
    "budgets": [
      {
        "id": "budget-acme-monthly",
        "max_limit": 2000.00,
        "reset_duration": "1M"
      },
      {
        "id": "budget-team-ml",
        "max_limit": 500.00,
        "reset_duration": "1M",
        "team_id": "team-ml"
      }
    ]
  }
}
```

***

## Projects

Declare projects that requests opt into for access and accounting. A project composes with what the caller already holds, spends against its own budgets, the calling principal's, or both as `accounting_mode` directs, and can divide every budget and rate limit it holds equally between its members.

<Note>
  `governance.projects` is an enterprise capability. Members are added from the dashboard: a project declared here starts with none, and membership cannot be declared in the file. The schema rejects a `members` key.
</Note>

```json theme={null}
{
  "governance": {
    "projects": [
      {
        "name": "atlas",
        "description": "Atlas research",
        "access_rule": "union",
        "split_policy": "equal",
        "calendar_aligned": true,
        "budgets": [{ "max_limit": 1000.00, "reset_duration": "1M" }],
        "rate_limit": { "request_max_limit": 600, "request_reset_duration": "1m" },
        "provider_configs": [
          {
            "provider_name": "openai",
            "all_models_allowed": false,
            "allowed_models": ["gpt-4o", "gpt-4o-mini"],
            "budgets": [{ "max_limit": 400.00, "reset_duration": "1M" }],
            "model_budgets": [
              { "model_name": "gpt-4o", "budgets": [{ "max_limit": 100.00, "reset_duration": "1d" }] }
            ]
          }
        ],
        "mcp_configs": [{ "mcp_client_name": "github", "tools_to_execute": ["*"] }]
      }
    ]
  }
}
```

Requests reference a project by name with the `x-bf-project-name` header.

### Project Fields

| Field              | Required | Description                                                                                                                                                                                                  |
| ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name`             | Yes      | Unique project name. Declarations are matched to stored projects by name                                                                                                                                     |
| `description`      | No       | Free-form description                                                                                                                                                                                        |
| `is_active`        | No       | Defaults to `true`                                                                                                                                                                                           |
| `expires_at`       | No       | RFC 3339 timestamp after which the project grants nothing                                                                                                                                                    |
| `access_rule`      | Yes      | `union` leaves the caller's own access untouched (a project with no provider or MCP config then only accounts for spend); `intersect` permits only what both the caller and the project allow                |
| `membership_mode`  | No       | `explicit` (default) consults the membership rows; `open` lets any caller opt in. An open project cannot use `split_policy: equal`                                                                           |
| `accounting_mode`  | No       | Which ledgers spend lands on: `both` (default), `project_only`, or `principal_only`                                                                                                                          |
| `split_policy`     | No       | `none` (default) shares every cap the project holds; `equal` gives every member an equal slice of every budget and rate limit at every tier: the project's own, each provider's, and each model's            |
| `calendar_aligned` | No       | Snap reset windows to calendar boundaries instead of rolling from first use                                                                                                                                  |
| `budgets`          | No       | Project-level spend caps, each with `max_limit` and `reset_duration`                                                                                                                                         |
| `rate_limit`       | No       | Request and token limits: `request_max_limit`, `request_reset_duration`, `token_max_limit`, `token_reset_duration`                                                                                           |
| `provider_configs` | No       | One entry per provider, see below                                                                                                                                                                            |
| `mcp_configs`      | No       | One entry per MCP client: `mcp_client_name` (as declared under `mcp.client_configs`) and `tools_to_execute` (`["*"]` grants every tool; empty or omitted grants none, regardless of the top-level `version`) |

Budgets and rate limits inside a project are declared without ids.

### Project Provider Config Fields

| Field                | Required | Description                                                                                                                                                    |
| -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider_name`      | Yes      | Provider this entry grants. A project names each provider at most once                                                                                         |
| `all_models_allowed` | No       | Allow every model of the provider                                                                                                                              |
| `allowed_models`     | No       | Allowed model names, ignored when `all_models_allowed` is true                                                                                                 |
| `blacklisted_models` | No       | Models blocked even if allowed; `["*"]` blocks all                                                                                                             |
| `key_ids`            | No       | Keys that may serve the request; `["*"]` allows all, empty or omitted allows none, regardless of the top-level `version`. Each id must belong to this provider |
| `weight`             | No       | Load-balancer seed weight; omit to leave the caller's own preference standing                                                                                  |
| `budgets`            | No       | Spend caps on this provider inside the project                                                                                                                 |
| `rate_limit`         | No       | Request and token limits on this provider inside the project                                                                                                   |
| `model_budgets`      | No       | Per-model caps under this provider: `model_name`, `budgets`, and an optional `rate_limit`. The `*` tier is not accepted here                                   |

### How projects are reconciled

* **Matched by name.** A name not in the database creates the project; a name already stored updates it when the declaration's hash differs from the one recorded at the last sync, and leaves it alone otherwise, so dashboard edits survive until the file changes.
* **Edits keep spend.** Budgets are paired with their stored rows by `reset_duration` (in declared order when a duration appears twice), provider configs by `provider_name`, model budgets by `model_name`, and MCP configs by client. A paired row is updated in place, so raising a cap does not forgive what was already spent against it. A budget, provider, or client the file stops declaring is removed.
* **Equal splits redivide in the background.** Changing a cap or the split policy of a project with `split_policy: equal` queues a recalculation of every member's share, which runs shortly after startup.
* **Members are never touched.** The file cannot add or remove members; the schema rejects a `members` key on a project.
* **With `source_of_truth: "config.json"`** and `governance.projects` present, declarations always overwrite the database and projects the file does not declare are deleted, members included.

***

## Full Governance Example

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