diff --git a/docs/changelogs/v1.5.0-prerelease3.mdx b/docs/changelogs/v1.5.0-prerelease3.mdx
index ea86d606f6..b85f049bfc 100644
--- a/docs/changelogs/v1.5.0-prerelease3.mdx
+++ b/docs/changelogs/v1.5.0-prerelease3.mdx
@@ -19,71 +19,33 @@ description: "v1.5.0-prerelease3 changelog - 2026-04-13"
## ✨ Features
-- **Object Store Log Store** — Logging plugin's log store can now offload payloads to object storage (S3/GCS/etc.) instead of bloating the database
-- **Azure Passthrough** — Azure passthrough support added
-- **Mistral OCR** — End-to-end support for the `/v1/ocr` endpoint (Mistral OCR)
-- **OpenRouter Embeddings** — Embeddings support added to the OpenRouter provider
-- **Redis TLS & Cluster Mode** — TLS and cluster-mode support for Redis; valkey-search query fix
-- **Parallel Model Catalog Sync** — Model catalog restructured with parallel syncs for faster startup and refreshes
-- **Dashboard CSV & PDF Export** — Export dashboard views to CSV or PDF
-- **OpenAPI Security Schemes** — Security schemes added to the OpenAPI spec; virtual key examples updated; API playground now only shows supported auth methods per endpoint
-- **Configurable API Docs Base URL** — Base URL for the hosted API documentation is configurable
-- **v1.4.x Allow-List Compatibility Mode** — New version-1 compatibility mode preserves v1.4.x allow-list semantics for smoother upgrades
-- **OAuth MCP Hints** — OAuth MCP client creation response now includes next-step hints
-- **272k Token Tier Pricing** — Pricing support for the 272k token tier
-- **Flex & Priority Tier Pricing** — Flex and priority tier pricing (including override fields)
-- **UI: Create-Provider Shortcut** — Empty state now links directly to provider creation
+- **OAuth MCP** - add next-step hints to OAuth MCP client creation response
+- **Azure passthrough** - added azure passthrough support
+- **272k token tier** - add 272k token tier pricing support in pricing
+- **Flex and priority tier support** - added flex and priority tier support in pricing
## 🐞 Fixed
-- **Streaming Post-Hook Race** — Fix race where fasthttp RequestCtx could be recycled before transport post-hooks completed in streaming goroutines; snapshots are now captured eagerly before the handler returns
-- **Streaming Blocker** — Fix a streaming blocker in the transport layer
+- **Streaming Post-Hook Race** — Fix race condition where fasthttp RequestCtx could be recycled before transport post-hooks complete in streaming goroutines; eagerly captures request/response snapshots before handler returns
- **Async User Values** — Propagate user values through all async inference handlers and job submissions
-- **Trace Completer Safety** — Trace completer accepts transport logs as a parameter instead of reading from a potentially recycled context
+- **Trace Completer Safety** — Refactor trace completer to accept transport logs as parameter instead of reading from potentially recycled context
- **Async Log Store Exceptions** — Fix exception handling in async log store jobs
-- **Model Alias Tracking** — Split `ModelRequested` into `OriginalModelRequested` and `ResolvedModelUsed` for accurate alias resolution tracking
-- **MCP Tool Discovery** — Add discovered tools and tool-name mapping columns to MCP clients
-- **OAuth Transient Failures** — Don't mark OAuth configs expired on transient network failures
-- **OAuth Session Cleanup** — Clean up OAuth sessions on virtual key deletion; fixes an associated race condition
-- **Pricing Sync Config** — Correctly apply `pricing_sync_interval` and support env variables in `pricing_url`
-- **Key Validation Logging** — Improved key validation error handling and logging
-- **DB Deadlock Prevention** — Replace find-then-upsert with atomic `ON CONFLICT` to prevent deadlocks
-- **LiteLLM Compat** — LiteLLM compatibility fixes
-- **SQLite Migration Fix** — Additional SQLite migration fixes
-- **CVE Fixes** — Dependency updates addressing reported CVEs
+- **Model Alias Tracking** — Split ModelRequested into OriginalModelRequested and ResolvedModelUsed for accurate model alias resolution tracking
+- **MCP Tool Discovery** — Add discovered tools and tool name mapping columns to MCP clients
-- feat: Azure passthrough support
-- feat: add end-to-end support for `/v1/ocr` (Mistral OCR)
-- feat: add Embeddings in OpenRouter provider
-- feat: add 272k token and priority tiers support in pricing
-- feat: add flex tier pricing support with flat rate structure
-- refactor: split `ModelRequested` into `OriginalModelRequested` and `ResolvedModelUsed` for model alias tracking
-- refactor: restructure model catalog with parallel syncs
+- refactor: split ModelRequested into OriginalModelRequested and ResolvedModelUsed for model alias tracking
- refactor: simplify Azure passthrough by removing redundant config nil checks
- refactor: simplify Mistral error parsing signature
-- refactor: add response backfill methods
-- fix: carry `ProviderResponseHeaders` through text completion response conversion
-- fix: streaming blocker
-- fix: core test fixes
-- fix: CVE / package updates
+- fix: carry ProviderResponseHeaders through text completion response conversion
- feat: add MCP client discovered tools and tool name mapping migration
-- feat: object store support for log_store (avoid bloating DB)
-- feat: add version 1 compatibility mode for v1.4.x allow-list semantics
-- feat: add flex and 272k token tier pricing override fields
-- fix: don't mark OAuth config expired on transient network failures
-- fix: cleanup OAuth sessions on virtual key deletion and prevent race condition
-- fix: replace find-then-upsert with atomic `ON CONFLICT` to prevent deadlocks
-- fix: correctly apply `pricing_sync_interval` and support env variables in `pricing_url`
- fix: exception handling in async log store jobs
-- fix: migration fix
-- refactor: improve key validation error handling and logging
-- refactor: model catalog Init API to use `SetShouldSyncGate` method
-- refactor: rename `DefaultPricingSyncInterval` to `DefaultSyncInterval`
+- refactor: model catalog Init API to use SetShouldSyncGate method
+- refactor: rename DefaultPricingSyncInterval to DefaultSyncInterval
@@ -99,12 +61,10 @@ description: "v1.5.0-prerelease3 changelog - 2026-04-13"
-- fix: litellm compat
- chore: upgraded core to v1.5.2 and framework to v1.3.2
-- feat: object store support for log_store (avoid bloating DB)
- chore: upgraded core to v1.5.2 and framework to v1.3.2
@@ -125,7 +85,6 @@ description: "v1.5.0-prerelease3 changelog - 2026-04-13"
-- feat: Redis TLS + cluster mode support; valkey-search query fix
- chore: upgraded core to v1.5.2 and framework to v1.3.2
diff --git a/docs/contributing/code-conventions.mdx b/docs/contributing/code-conventions.mdx
index 7f8a51c6e6..c8a73b2774 100644
--- a/docs/contributing/code-conventions.mdx
+++ b/docs/contributing/code-conventions.mdx
@@ -321,7 +321,7 @@ make test-all
### TypeScript/React
- Use ESLint configuration from project
-- Run Prettier for formatting
+- Run `npm run format` for formatting
- Ensure TypeScript compilation succeeds
## Key Principles
diff --git a/docs/contributing/setting-up-repo.mdx b/docs/contributing/setting-up-repo.mdx
index 0051ddf9d1..d4ea991fb0 100644
--- a/docs/contributing/setting-up-repo.mdx
+++ b/docs/contributing/setting-up-repo.mdx
@@ -84,7 +84,7 @@ This command will:
1. Install UI dependencies automatically
2. Install Air for hot reloading
3. Set up the Go workspace with local modules
-4. Start the Next.js development server (port 3000)
+4. Start the Vite development server (port 3000)
5. Start the API server with UI proxy (port 8080)
**Access the application at:** http://localhost:8080
@@ -350,7 +350,7 @@ make setup-workspace
**UI dependency issues:**
```bash
# Clean and reinstall UI dependencies
-rm -rf ui/node_modules ui/.next
+rm -rf ui/node_modules
make install-ui
```
diff --git a/docs/docs.json b/docs/docs.json
index 0a537b322c..155b91a096 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -60,9 +60,7 @@
{
"group": "Overview",
"icon": "book-open-cover",
- "pages": [
- "overview"
- ]
+ "pages": ["overview"]
},
{
"group": "Quick Start",
@@ -102,16 +100,12 @@
{
"group": "Migration Guides",
"icon": "arrow-up-right-dots",
- "pages": [
- "migration-guides/v1.5.0"
- ]
+ "pages": ["migration-guides/v1.5.0"]
},
{
"group": "SDK Integrations",
"icon": "plug",
- "pages": [
- "integrations/what-is-an-integration"
- ]
+ "pages": ["integrations/what-is-an-integration"]
},
{
"group": "Providers & Guides",
@@ -184,10 +178,7 @@
{
"group": "Writing Plugins",
"icon": "code",
- "pages": [
- "plugins/writing-go-plugin",
- "plugins/writing-wasm-plugin"
- ]
+ "pages": ["plugins/writing-go-plugin", "plugins/writing-wasm-plugin"]
},
"plugins/migration-guide"
]
@@ -217,18 +208,12 @@
{
"group": "Prompt Repository",
"icon": "folder",
- "pages": [
- "features/prompt-repository/playground",
- "features/prompt-repository/prompts-plugin"
- ]
+ "pages": ["features/prompt-repository/playground", "features/prompt-repository/prompts-plugin"]
},
{
"group": "Plugins",
"icon": "puzzle-piece",
- "pages": [
- "features/plugins/mocker",
- "features/plugins/jsonparser"
- ]
+ "pages": ["features/plugins/mocker", "features/plugins/jsonparser"]
}
]
},
@@ -239,16 +224,13 @@
"enterprise/guardrails",
"enterprise/clustering",
"enterprise/adaptive-load-balancing",
+ "enterprise/user-provisioning",
{
"group": "Advanced Governance",
"icon": "shield-check",
- "pages": [
- "enterprise/advanced-governance",
- "enterprise/rbac"
- ]
+ "pages": ["enterprise/advanced-governance", "enterprise/rbac"]
},
"enterprise/mcp-with-fa",
- "enterprise/vault-support",
"enterprise/invpc-deployments",
"enterprise/custom-plugins",
"enterprise/audit-logs",
@@ -286,33 +268,22 @@
{
"group": "OpenAI SDK",
"icon": "openai",
- "pages": [
- "integrations/openai-sdk/overview",
- "integrations/openai-sdk/files-and-batch"
- ]
+ "pages": ["integrations/openai-sdk/overview", "integrations/openai-sdk/files-and-batch"]
},
{
"group": "Anthropic SDK",
"icon": "asterisk",
- "pages": [
- "integrations/anthropic-sdk/overview",
- "integrations/anthropic-sdk/files-and-batch"
- ]
+ "pages": ["integrations/anthropic-sdk/overview", "integrations/anthropic-sdk/files-and-batch"]
},
{
"group": "Bedrock SDK",
"icon": "aws",
- "pages": [
- "integrations/bedrock-sdk/overview",
- "integrations/bedrock-sdk/files-and-batch"
- ]
+ "pages": ["integrations/bedrock-sdk/overview", "integrations/bedrock-sdk/files-and-batch"]
},
{
"group": "GenAI SDK",
"icon": "diamond",
- "pages": [
- "integrations/genai-sdk/overview"
- ]
+ "pages": ["integrations/genai-sdk/overview"]
},
"integrations/litellm-sdk",
"integrations/langchain-sdk",
@@ -325,7 +296,9 @@
"icon": "id-card",
"pages": [
"enterprise/setting-up-okta",
- "enterprise/setting-up-entra"
+ "enterprise/setting-up-entra",
+ "enterprise/setting-up-zitadel",
+ "enterprise/setting-up-google-workspace"
]
},
{
@@ -338,16 +311,6 @@
"integrations/guardrails/patronus-ai"
]
},
- {
- "group": "Secret Management (Vaults)",
- "icon": "vault",
- "pages": [
- "integrations/vaults/hashicorp-vault",
- "integrations/vaults/aws-secrets-manager",
- "integrations/vaults/google-secret-manager",
- "integrations/vaults/azure-key-vault"
- ]
- },
{
"group": "Observability",
"icon": "binoculars",
@@ -467,9 +430,7 @@
{
"tab": "Security",
"icon": "shield",
- "pages": [
- "security"
- ]
+ "pages": ["security"]
},
{
"tab": "Benchmarks",
@@ -629,10 +590,7 @@
},
{
"group": "September 2025",
- "pages": [
- "changelogs/v1.2.22",
- "changelogs/v1.2.21"
- ]
+ "pages": ["changelogs/v1.2.22", "changelogs/v1.2.21"]
}
]
},
@@ -747,7 +705,7 @@
},
{
"source": "/features/enterprise/scim",
- "destination": "/enterprise/setting-up-okta"
+ "destination": "/enterprise/user-provisioning"
},
{
"source": "/enterprise/intelligent-load-balancing",
diff --git a/docs/enterprise/audit-logs.mdx b/docs/enterprise/audit-logs.mdx
index 66e3de9c08..79f4c83951 100644
--- a/docs/enterprise/audit-logs.mdx
+++ b/docs/enterprise/audit-logs.mdx
@@ -50,7 +50,7 @@ icon: "scroll"
- Rate limit changes
- Provider key updates
- Guardrail configuration changes
-- SAML/OIDC settings updates
+- SCIM/OIDC settings updates
### Data Access Events
- PII detection and handling
@@ -80,25 +80,10 @@ icon: "scroll"
```json
{
- "enterprise": {
- "audit_logs": {
- "enabled": true,
- "retention": {
- "duration": "365d",
- "archive_after": "90d"
- },
- "capture": {
- "authentication": true,
- "authorization": true,
- "configuration_changes": true,
- "data_access": true,
- "security_events": true
- },
- "immutability": {
- "enabled": true,
- "verification_method": "cryptographic_hash"
- }
- }
+ "audit_logs": {
+ "disabled": false,
+ "hmac_key": "env.AUDIT_HMAC_KEY",
+ "retention_days": 365
}
}
```
@@ -126,64 +111,13 @@ BIFROST_AUDIT_IMMUTABLE=true
-### Advanced Configuration
+### Configuration Fields
-```json
-{
- "audit_logs": {
- "enabled": true,
- "backup": {
- "type": "s3",
- "bucket": "bifrost-audit-logs",
- "region": "us-west-2",
- "encryption": "AES256"
- }
- },
- "retention": {
- "duration": "365d",
- "archive_after": "90d",
- "delete_after": "2555d",
- "hot_storage_days": 30
- },
- "capture": {
- "authentication": {
- "enabled": true,
- "include_failed_attempts": true,
- "track_session_duration": true
- },
- "authorization": {
- "enabled": true,
- "log_allowed_access": false,
- "log_denied_access": true
- },
- "configuration_changes": {
- "enabled": true,
- "track_before_after": true,
- "exclude_fields": ["password", "api_key"]
- },
- "data_access": {
- "enabled": true,
- "log_pii_detection": true,
- "log_sensitive_operations": true
- },
- "security_events": {
- "enabled": true,
- "severity_threshold": "medium"
- }
- },
- "enrichment": {
- "geo_location": true,
- "user_agent_parsing": true,
- "ip_reputation": true
- },
- "immutability": {
- "enabled": true,
- "verification_method": "cryptographic_hash",
- "signing_key": "${AUDIT_LOG_SIGNING_KEY}"
- }
- }
-}
-```
+| Field | Type | Description |
+|-------|------|-------------|
+| `disabled` | boolean | When `true`, audit logging is turned off. Default: `false`. |
+| `hmac_key` | string | HMAC secret key used to sign audit events. Minimum 32 bytes. Supports `env.` prefix for environment variables (e.g. `env.AUDIT_HMAC_KEY`). |
+| `retention_days` | integer | Days to retain audit log entries. `0` disables retention-based cleanup. |
---
diff --git a/docs/enterprise/clustering.mdx b/docs/enterprise/clustering.mdx
index 3c037a3390..73ddc7e8ae 100644
--- a/docs/enterprise/clustering.mdx
+++ b/docs/enterprise/clustering.mdx
@@ -80,8 +80,7 @@ The new clustering configuration uses a `cluster_config` object with integrated
"discovery": {
"enabled": true,
"type": "kubernetes",
- "service_name": "bifrost-cluster",
- // Discovery-specific configuration here
+ "service_name": "bifrost-cluster"
},
"gossip": {
"port": 10101,
@@ -95,6 +94,8 @@ The new clustering configuration uses a `cluster_config` object with integrated
}
```
+Discovery-specific fields (e.g. `k8s_label_selector`, `consul_address`, `etcd_endpoints`) slot into the `discovery` object alongside `type` — see each method's section below.
+
### Common Discovery Configuration Fields
All discovery methods support these common fields:
@@ -173,7 +174,12 @@ Kubernetes discovery uses the K8s API to automatically discover pods based on la
"k8s_label_selector": "app=bifrost"
},
"gossip": {
- "port": 10101
+ "port": 10101,
+ "config": {
+ "timeout_seconds": 10,
+ "success_threshold": 3,
+ "failure_threshold": 3
+ }
}
}
}
@@ -367,7 +373,12 @@ Consul discovery integrates with HashiCorp Consul for service registration and d
"consul_address": "consul.service.consul:8500"
},
"gossip": {
- "port": 10101
+ "port": 10101,
+ "config": {
+ "timeout_seconds": 10,
+ "success_threshold": 3,
+ "failure_threshold": 3
+ }
}
}
}
@@ -510,7 +521,12 @@ etcd discovery uses etcd's distributed key-value store for service registration
"dial_timeout": "10s"
},
"gossip": {
- "port": 10101
+ "port": 10101,
+ "config": {
+ "timeout_seconds": 10,
+ "success_threshold": 3,
+ "failure_threshold": 3
+ }
}
}
}
@@ -661,7 +677,12 @@ DNS discovery uses standard DNS resolution to discover cluster nodes. Works with
"bind_port": 10101
},
"gossip": {
- "port": 10101
+ "port": 10101,
+ "config": {
+ "timeout_seconds": 10,
+ "success_threshold": 3,
+ "failure_threshold": 3
+ }
}
}
}
@@ -806,7 +827,12 @@ UDP broadcast discovery automatically finds nodes on the same local network usin
"dial_timeout": "10s"
},
"gossip": {
- "port": 10101
+ "port": 10101,
+ "config": {
+ "timeout_seconds": 10,
+ "success_threshold": 3,
+ "failure_threshold": 3
+ }
}
}
}
@@ -937,7 +963,12 @@ mDNS (Multicast DNS) provides zero-configuration service discovery on local netw
"dial_timeout": "10s"
},
"gossip": {
- "port": 10101
+ "port": 10101,
+ "config": {
+ "timeout_seconds": 10,
+ "success_threshold": 3,
+ "failure_threshold": 3
+ }
}
}
}
@@ -1353,7 +1384,12 @@ sudo cat > /etc/bifrost/config.json < 0",
- "apply_to": "input",
- "sampling_rate": 100,
- "timeout": 2000,
- "provider_config_ids": [1]
- }
- ]
- }
+ "guardrails_config": {
+ "guardrail_rules": [
+ {
+ "id": 1,
+ "name": "Block PII in Prompts",
+ "description": "Prevent PII from being sent to LLM providers",
+ "enabled": true,
+ "cel_expression": "request.messages.exists(m, m.role == \"user\")",
+ "apply_to": "input",
+ "sampling_rate": 100,
+ "timeout": 5000,
+ "provider_config_ids": [1, 2]
+ },
+ {
+ "id": 2,
+ "name": "Content Filter for Responses",
+ "description": "Filter harmful content from LLM responses",
+ "enabled": true,
+ "cel_expression": "true",
+ "apply_to": "output",
+ "sampling_rate": 100,
+ "timeout": 3000,
+ "provider_config_ids": [2]
+ },
+ {
+ "id": 3,
+ "name": "Prompt Injection Detection",
+ "description": "Detect and block prompt injection attempts",
+ "enabled": true,
+ "cel_expression": "request.messages.size() > 0",
+ "apply_to": "input",
+ "sampling_rate": 100,
+ "timeout": 2000,
+ "provider_config_ids": [1]
+ }
+ ]
}
}
```
@@ -279,27 +277,26 @@ curl -X DELETE http://localhost:8080/api/enterprise/guardrails/rules/1
```yaml
-enterprise:
- guardrails:
- rules:
- - id: 1
- name: "Block PII in Prompts"
- description: "Prevent PII from being sent to LLM providers"
- enabled: true
- cel_expression: "request.messages.exists(m, m.role == 'user')"
- apply_to: "input"
- sampling_rate: 100
- timeout: 5000
- provider_config_ids: [1, 2]
- - id: 2
- name: "Content Filter for Responses"
- description: "Filter harmful content from LLM responses"
- enabled: true
- cel_expression: "true"
- apply_to: "output"
- sampling_rate: 100
- timeout: 3000
- provider_config_ids: [2]
+guardrails_config:
+ guardrail_rules:
+ - id: 1
+ name: "Block PII in Prompts"
+ description: "Prevent PII from being sent to LLM providers"
+ enabled: true
+ cel_expression: "request.messages.exists(m, m.role == 'user')"
+ apply_to: "input"
+ sampling_rate: 100
+ timeout: 5000
+ provider_config_ids: [1, 2]
+ - id: 2
+ name: "Content Filter for Responses"
+ description: "Filter harmful content from LLM responses"
+ enabled: true
+ cel_expression: "true"
+ apply_to: "output"
+ sampling_rate: 100
+ timeout: 3000
+ provider_config_ids: [2]
```
@@ -464,9 +461,8 @@ curl -X DELETE http://localhost:8080/api/enterprise/guardrails/providers/1
```json
{
- "enterprise": {
- "guardrails": {
- "guardrail_providers": [
+ "guardrails_config": {
+ "guardrail_providers": [
{
"id": 1,
"provider_name": "bedrock",
@@ -508,61 +504,69 @@ curl -X DELETE http://localhost:8080/api/enterprise/guardrails/providers/1
"professional_tone": "Ensure responses maintain a professional tone"
}
}
+ },
+ {
+ "id": 4,
+ "provider_name": "patronus_ai",
+ "policy_name": "Hallucination Detection",
+ "enabled": true,
+ "config": {
+ "api_key": "${PATRONUS_API_KEY}",
+ "api_endpoint": "https://api.patronus.ai/v1"
+ }
}
]
}
}
-}
```
```yaml
-enterprise:
- guardrails:
- providers:
- - id: 1
- provider_name: "bedrock"
- policy_name: "PII Detection Profile"
- enabled: true
- config:
- guardrail_arn: "arn:aws:bedrock:us-east-1:123456789:guardrail/abc123"
- guardrail_version: "1"
- region: "us-east-1"
- # AWS Authentication (choose one method):
- # Option 1: Explicit credentials
- access_key: "${AWS_ACCESS_KEY_ID}"
- secret_key: "${AWS_SECRET_ACCESS_KEY}"
- # Option 2: IAM Role - omit access_key and secret_key
- # (Bifrost will use IAM credentials from the environment)
- - id: 2
- provider_name: "azure"
- policy_name: "Content Safety Profile"
- enabled: true
- config:
- endpoint: "https://your-resource.cognitiveservices.azure.com/"
- api_key: "${AZURE_CONTENT_SAFETY_API_KEY}"
- analyze_enabled: true
- analyze_severity_threshold: "medium"
- jailbreak_shield_enabled: true
- - id: 3
- provider_name: "grayswan"
- policy_name: "Custom Safety Rules"
- enabled: true
- config:
- api_key: "${GRAYSWAN_API_KEY}"
- violation_threshold: 0.5
- reasoning_mode: "hybrid"
- rules:
- no_pii: "Do not allow personally identifiable information"
- professional_tone: "Ensure responses maintain a professional tone"
- - id: 4
- provider_name: "patronus_ai"
- policy_name: "Hallucination Detection"
- enabled: true
- config:
- api_endpoint: "https://api.patronus.ai/v1"
+guardrails_config:
+ guardrail_providers:
+ - id: 1
+ provider_name: "bedrock"
+ policy_name: "PII Detection Profile"
+ enabled: true
+ config:
+ guardrail_arn: "arn:aws:bedrock:us-east-1:123456789:guardrail/abc123"
+ guardrail_version: "1"
+ region: "us-east-1"
+ # AWS Authentication (choose one method):
+ # Option 1: Explicit credentials
+ access_key: "${AWS_ACCESS_KEY_ID}"
+ secret_key: "${AWS_SECRET_ACCESS_KEY}"
+ # Option 2: IAM Role - omit access_key and secret_key
+ # (Bifrost will use IAM credentials from the environment)
+ - id: 2
+ provider_name: "azure"
+ policy_name: "Content Safety Profile"
+ enabled: true
+ config:
+ endpoint: "https://your-resource.cognitiveservices.azure.com/"
+ api_key: "${AZURE_CONTENT_SAFETY_API_KEY}"
+ analyze_enabled: true
+ analyze_severity_threshold: "medium"
+ jailbreak_shield_enabled: true
+ - id: 3
+ provider_name: "grayswan"
+ policy_name: "Custom Safety Rules"
+ enabled: true
+ config:
+ api_key: "${GRAYSWAN_API_KEY}"
+ violation_threshold: 0.5
+ reasoning_mode: "hybrid"
+ rules:
+ no_pii: "Do not allow personally identifiable information"
+ professional_tone: "Ensure responses maintain a professional tone"
+ - id: 4
+ provider_name: "patronus_ai"
+ policy_name: "Hallucination Detection"
+ enabled: true
+ config:
+ api_endpoint: "https://api.patronus.ai/v1"
```
diff --git a/docs/enterprise/rbac.mdx b/docs/enterprise/rbac.mdx
index e1f4f61c1e..86cd19e4a9 100644
--- a/docs/enterprise/rbac.mdx
+++ b/docs/enterprise/rbac.mdx
@@ -14,7 +14,7 @@ Role-Based Access Control (RBAC) in Bifrost Enterprise provides fine-grained acc
- **Audit-Ready** - Track who has access to what for compliance requirements
- **Flexible Role Design** - Use system roles or create custom roles for your organization
-RBAC integrates seamlessly with [Identity Provider authentication](./advanced-governance#identity-provider-integration), automatically assigning roles based on your IdP groups and claims.
+RBAC integrates seamlessly with [User Provisioning (SCIM)](./user-provisioning), automatically assigning roles based on your IdP groups and claims.
---
@@ -169,7 +169,7 @@ Custom "Ops" role with:
## Integration with Identity Providers
-When using [Okta](./setting-up-okta) or [Microsoft Entra](./setting-up-entra) for authentication, roles can be automatically assigned based on:
+When using [User Provisioning (SCIM)](./user-provisioning) with Okta, Entra, Zitadel, Keycloak, or Google Workspace, roles can be automatically assigned based on:
- **IdP Groups** - Map identity provider groups to Bifrost roles
- **App Roles** - Sync application roles from your IdP
diff --git a/docs/enterprise/setting-up-entra.mdx b/docs/enterprise/setting-up-entra.mdx
index a796138b14..9d2848ee21 100644
--- a/docs/enterprise/setting-up-entra.mdx
+++ b/docs/enterprise/setting-up-entra.mdx
@@ -51,7 +51,11 @@ You can add an app icon to make the application easily recognizable. The Bifrost
---
-## Step 2: Create App Roles
+## Step 2: Create App Roles (Optional)
+
+
+ This step is optional. You can create custom roles if thats the preferred way. Or you can map any attribute to role/team/business unit. Role mapping is required step.
+
Configure roles in Entra that map to Bifrost's role hierarchy (Admin, Developer, Viewer).
@@ -93,10 +97,6 @@ Configure roles in Entra that map to Bifrost's role hierarchy (Admin, Developer,
| **Description** | Admin role on Bifrost |
| **State** | Enabled |
-
-The role **Value** must be lowercase (`admin`, `developer`, `viewer`) to match Bifrost's role resolution logic. Users with multiple roles will be assigned the highest privilege role.
-
-
---
## Step 3: Enable Assignment Required
@@ -168,6 +168,10 @@ Ensure your application has the necessary permissions.
## Step 6: Configure Token Claims (Optional)
+
+ Groups and other attributes are required in the claim when you configure their mapping in Bifrost.
+
+
By default, Entra includes the `roles` claim when app roles are assigned. To include group memberships for team synchronization:
1. Go to **Token configuration**
@@ -177,10 +181,6 @@ By default, Entra includes the `roles` claim when app roles are assigned. To inc
- For token type, enable **ID** and **Access**
4. Click **Add**
-
-Group IDs from Entra will be used as team IDs in Bifrost. You may want to create groups in Entra that correspond to your teams.
-
-
---
## Step 7: Assign Users and Roles
@@ -203,12 +203,45 @@ You can assign roles to groups for easier management. All users in a group will
---
-## Step 8: Configure Bifrost
+## Step 8: Configure App Manifest
+
+
+
+
+
+You will need to make 2 changes in the app manifest
+
+```json
+"requestedAccessTokenVersion": 2
+```
+
+and
+
+```json
+"optionalClaims": {
+ "idToken": [
+ {
+ "name": "roles",
+ "source": null,
+ "essential": false,
+ "additionalProperties": []
+ }
+ ],
+ "accessToken": [],
+ "saml2Token": []
+}
+```
+
+## Step 9: Configure Bifrost
Now configure Bifrost to use Microsoft Entra as the identity provider.
### Using the Bifrost UI
+
+
+
+
1. Navigate to **Governance** → **User Provisioning** in your Bifrost dashboard
2. Select **Microsoft Entra** as the SCIM Provider
3. Enter the following configuration:
@@ -221,8 +254,9 @@ Now configure Bifrost to use Microsoft Entra as the identity provider.
| **Audience** | Your Client ID (optional, defaults to Client ID) |
| **App ID URI** | `api://{client-id}` (optional, for v1.0 tokens) |
-4. Toggle **Enabled** to activate the provider
-5. Click **Save Configuration**
+5. **Verify** configuration and see if you get any errors. Make sure you get no errors/warnings.
+6. Toggle **Enabled** to activate the provider
+7. Click **Save Configuration**
After saving, you'll need to restart your Bifrost server for the changes to take effect.
@@ -236,33 +270,72 @@ After saving, you'll need to restart your Bifrost server for the changes to take
| `clientId` | Yes | Application (client) ID |
| `clientSecret` | Yes | Client secret for OAuth authentication |
| `audience` | No | JWT audience for validation (defaults to clientId) |
-| `appIdUri` | No | App ID URI for v1.0 tokens (e.g., `api://{clientId}`) |
-| `userIdField` | No | JWT claim for user ID (default: `oid`) |
-| `rolesField` | No | JWT claim for roles (default: `roles`) |
-| `teamIdsField` | No | JWT claim for group/team IDs (default: `groups`) |
+| `attributeRoleMappings` | Yes | Ordered list of attribute→role mappings. First match wins. |
+| `attributeTeamMappings` | No | Attribute→team mappings (all matches apply). |
+| `attributeBusinessUnitMappings` | No | Attribute→business-unit mappings (all matches apply). |
---
-## Role Mapping
+### Attribute Mappings
+
+Attribute mappings let you translate Okta claim values into Bifrost roles, teams, or business units without restructuring your Okta claims. Bifrost supports three mapping types:
-Bifrost automatically maps Entra app roles to its internal role hierarchy:
+- **`attributeRoleMappings`**: map a claim value to a Bifrost role (Admin, Developer, Viewer, or a custom role)
+- **`attributeTeamMappings`**: map a claim value to a Bifrost team
+- **`attributeBusinessUnitMappings`**: map a claim value to a Bifrost business unit
-| Entra Role Value | Bifrost Role | Privilege Level |
-|------------------|--------------|-----------------|
-| `admin` | Admin | Highest |
-| `developer` | Developer | Medium |
-| `viewer` | Viewer | Lowest |
+These mappings work with any Okta claim — the `groups` claim from Step 5, the custom `role` claim from Step 4, or any other claim your authorization server includes in the token (e.g., `department`, `organization`).
-**Multiple Roles:** If a user has multiple roles assigned, Bifrost automatically selects the highest privilege role. For example, a user with both `viewer` and `developer` roles will be assigned the Developer role in Bifrost.
+To configure attribute mappings:
-**Default Role:** Users without any assigned role will default to the Viewer role.
+1. In the User Provisioning configuration, scroll down to **Attribute Mappings**
+2. Click **Add Mapping** under the relevant mapping type (Role, Team, or Business Unit)
+3. Enter the **Attribute** (the claim name from the token), the **Value** to match, and the target **Role**, **Team**, or **Business Unit**
+4. Repeat for each rule you need
+
+
+
+
+
+
+ When you mark value as "*" - the claim value is mapped as is to the entity name. Values comparisons are case-insensitive.
+
+
+### Custom attribute mapping
+
+You can also map any custom attributes to any entity (role, team or business unit). Make sure these are configured to send back to Bifrost in token configuration.
+
+
+
+
+
+
+#### Evaluation rules
+
+- **Role mappings**: Ordered, first match wins. If no rule matches, users are not allowed to login into the system.
+- **Team and business unit mappings**: All matching rules apply — users can be placed on multiple teams and business units simultaneously.
+- **Claim values**: Can be strings, arrays, or nested objects. Bifrost resolves dotted paths (e.g., `realm_access.roles`).
+
+
+ If a user matches multiple role mapping rules, the highest privilege role is assigned. If no
+ mapping matches, the first user to sign in receives the **Admin** role, and subsequent users receive the **Viewer**
+ role.
+
+
+5. Click **Save Configuration**
---
## Testing the Integration
1. Open your Bifrost dashboard in a new browser or incognito window
-2. You should be redirected to Microsoft login
+2. You should be redirected to Entra for authentication
3. Log in with an assigned user
4. After successful authentication, you'll be redirected back to Bifrost
5. Verify the user appears in the Bifrost users list with the correct role
@@ -288,23 +361,6 @@ Bifrost automatically maps Entra app roles to its internal role hierarchy:
- Ensure you're using the secret **Value**, not the secret ID
- Check for any leading/trailing whitespace when copying
-### Roles not appearing in token
-
-- Ensure users are assigned to the Enterprise Application with a role
-- Verify app roles are created with the correct lowercase values
-- Check that "Assignment required" is enabled
-
-### "AADSTS70011: The provided request includes an invalid scope"
-
-- This usually happens when mixing `.default` scope with other scopes
-- Bifrost handles this automatically - ensure you're using the latest version
-
-### Groups not syncing as teams
-
-- Verify the groups claim is configured in Token configuration
-- Ensure users are members of the groups
-- Check that groups are created and assigned in Entra
-
### Token validation errors
- Ensure the Tenant ID matches your Azure directory
@@ -315,6 +371,7 @@ Bifrost automatically maps Entra app roles to its internal role hierarchy:
## Next Steps
+- **[User Provisioning (SCIM)](./user-provisioning)** - Overview of SCIM in Bifrost and alternative identity providers
- **[Advanced Governance](./advanced-governance)** - Learn about user budgets and compliance features
- **[Role-Based Access Control](./advanced-governance#role-hierarchy)** - Understand the Admin, Developer, Viewer hierarchy
- **[Audit Logs](./audit-logs)** - Monitor user authentication and activity
diff --git a/docs/enterprise/setting-up-google-workspace.mdx b/docs/enterprise/setting-up-google-workspace.mdx
new file mode 100644
index 0000000000..18fcbe7f9b
--- /dev/null
+++ b/docs/enterprise/setting-up-google-workspace.mdx
@@ -0,0 +1,232 @@
+---
+title: "Setting up Google Workspace"
+description: "Step-by-step guide to configure Google Workspace as your identity provider for Bifrost Enterprise SSO and Directory-based user provisioning."
+icon: "google"
+---
+
+## Overview
+
+This guide walks you through configuring **Google Workspace** as your identity provider for Bifrost Enterprise. The integration has two pieces:
+
+1. **OAuth 2.0 login** — users sign in to Bifrost with their Google Workspace accounts via a Google OAuth Client ID.
+2. **Directory API provisioning (optional)** — a Google **service account** with domain-wide delegation lets Bifrost list users and groups from the Workspace directory for bulk import and team sync.
+
+You can run login-only (no service account) or full provisioning (with service account + domain-wide delegation).
+
+## Prerequisites
+
+- A Google Workspace domain with **Super Admin** access to the Admin console
+- A Google Cloud project where you can create OAuth clients and service accounts
+- Bifrost Enterprise deployed and accessible
+- The redirect URI for your Bifrost instance (e.g. `https://your-bifrost-domain.com/login`)
+- Bifrost [roles](./rbac) created for the roles you plan to map
+
+---
+
+## Step 1: Configure the OAuth consent screen
+
+1. In the Google Cloud Console, go to **APIs & Services → OAuth consent screen**.
+
+
+
+
+
+2. Choose **Internal** if you only want Workspace users, or **External** otherwise.
+3. Fill in App name, support email, and developer contact.
+4. Add the scopes: `openid`, `profile`, `email`.
+5. Save.
+
+---
+
+## Step 2: Create an OAuth Client ID
+
+1. Open **APIs & Services → Credentials → Create credentials → OAuth client ID**.
+
+
+
+
+
+2. Configure:
+
+| Field | Value |
+| --- | --- |
+| **Application type** | Web application |
+| **Name** | Bifrost Enterprise |
+| **Authorized JavaScript origins** | `https://your-bifrost-domain.com` |
+| **Authorized redirect URIs** | `https://your-bifrost-domain.com/login` |
+
+3. Save and copy the **Client ID** and **Client Secret**.
+
+---
+
+## Step 3: (Optional) Create a service account for Directory API access
+
+Skip this section if you only want SSO login without directory-based user import.
+
+1. Go to **IAM & Admin → Service Accounts → Create service account**.
+
+
+
+
+
+2. Give it a name (e.g. `bifrost-provisioning`). You can skip the "Grant this service account access to project" step — no GCP IAM roles are required; access is granted via domain-wide delegation in Step 5.
+3. Open the service account → **Keys → Add Key → Create new key → JSON**. Download and store the JSON file securely.
+4. From the service account **Details** tab, copy the **Unique ID** (a numeric value, **not** the email or OAuth Client ID).
+
+---
+
+## Step 4: Enable the Admin SDK API
+
+If you're using the service account path:
+
+1. Open **APIs & Services → Library**.
+2. Search for **Admin SDK API** and click **Enable**.
+
+---
+
+## Step 5: Set up domain-wide delegation
+
+1. In the [Google Admin Console](https://admin.google.com), go to **Security → Access and data control → API controls → Manage Domain Wide Delegation**.
+
+
+
+
+
+2. Click **Add new**.
+3. Enter the service account's **Unique ID** (from Step 3).
+4. Add these OAuth scopes (copy the full URLs, comma-separated):
+
+```
+https://www.googleapis.com/auth/admin.directory.user.readonly,
+https://www.googleapis.com/auth/admin.directory.group.readonly,
+https://www.googleapis.com/auth/admin.directory.group.member.readonly
+```
+
+5. **Authorize**.
+
+
+ Domain-wide delegation requires impersonating an admin user. Pick an admin email that will persist (e.g. a dedicated `sso-admin@company.com`) — Bifrost uses this as the **Admin Email** in configuration.
+
+
+---
+
+## Step 6: Configure Bifrost
+
+### Using the Bifrost dashboard
+
+1. In Bifrost, go to **Governance → User Provisioning**.
+2. Select **Google Workspace** as the SCIM Provider.
+3. Fill in the fields:
+
+| Field | Value |
+| --- | --- |
+| **Domain** | Your Google Workspace primary domain (e.g. `company.com`) |
+| **Client ID** | OAuth Client ID from Step 2 |
+| **Client Secret** | OAuth Client Secret from Step 2 |
+| **Audience** | Optional override (defaults to Client ID) |
+| **Admin Email** | Admin user to impersonate for Directory API (Step 5) |
+| **Service Account Source** | Choose one: Paste JSON / Environment variable / File path |
+| **Service Account JSON / Env Var / File** | The value for the chosen source |
+
+
+
+
+
+4. Click **Verify** — Bifrost validates the OAuth client and, if a service account is provided, attempts a Directory API impersonation to confirm delegation is working.
+5. Configure **Attribute → Role / Team / Business Unit** mappings to map groups or organizational units to Bifrost roles and teams.
+6. Toggle **Enabled** and click **Save Configuration**.
+
+### Using `config.json`
+
+```json
+{
+ "scim_config": {
+ "enabled": true,
+ "provider": "google",
+ "config": {
+ "domain": "company.com",
+ "clientId": "123-abc.apps.googleusercontent.com",
+ "clientSecret": "${GOOGLE_WORKSPACE_CLIENT_SECRET}",
+ "adminEmail": "sso-admin@company.com",
+ "serviceAccountEnvVar": "GOOGLE_SA_JSON",
+ "teamIdsField": "groups"
+ }
+ }
+}
+```
+
+Pick one of the three service-account sources: `serviceAccountJson` (raw JSON string), `serviceAccountEnvVar` (env var name holding the JSON), or `serviceAccountFile` (absolute path to the key file).
+
+
+### Custom attribute mapping
+
+You can also map any custom attributes to any entity (role, team or business unit). Make sure these are configured to send back to Bifrost in token configuration.
+
+
+
+
+
+### Configuration reference
+
+| Field | Required | Description |
+| --- | --- | --- |
+| `domain` | Yes | Google Workspace primary domain (e.g. `company.com`). |
+| `clientId` | Yes | OAuth 2.0 Web Client ID from Step 2. |
+| `clientSecret` | Yes | Client Secret — required for token revocation and for confidential server-side flows. |
+| `audience` | No | Expected JWT audience. Defaults to `clientId`. |
+| `adminEmail` | Yes | Workspace admin to impersonate via domain-wide delegation. Required when any service-account field is set. |
+| `serviceAccountJson` | One of 3 | Raw JSON string of the service account key. |
+| `serviceAccountEnvVar` | One of 3 | Name of the environment variable containing the JSON. |
+| `serviceAccountFile` | One of 3 | Absolute path to the JSON key file on the Bifrost host. |
+| `attributeRoleMappings` | Yes | Ordered list of attribute→role mappings. |
+| `attributeTeamMappings` | No | Attribute→team mappings (all matches apply). |
+| `attributeBusinessUnitMappings` | No | Attribute→business-unit mappings (all matches apply). |
+
+
+ Bifrost rejects configs that set a service-account credential source without `adminEmail` — domain-wide delegation cannot work without an impersonation subject.
+
+
+---
+
+## Testing the Integration
+
+1. Open the Bifrost dashboard in an incognito window.
+2. You're redirected to `accounts.google.com`; sign in with a Workspace user.
+3. Verify you land on the Bifrost dashboard and appear under **Governance → Users**.
+4. If provisioning is configured, open **Governance → User Provisioning → Import Users**, filter by a Workspace group, click **Preview**, and confirm users show up.
+
+---
+
+## Troubleshooting
+
+### `admin_policy_enforced` or `access_denied` during OAuth
+
+- The Workspace admin has blocked third-party OAuth apps. In the Admin Console, go to **Security → Access and data control → API controls** and allow the Bifrost OAuth client.
+
+### `unauthorized_client: Client is unauthorized to retrieve access tokens`
+
+- The service account Unique ID and scopes in **Domain-Wide Delegation** don't match. Re-enter the Unique ID (the numeric value from the service account's **Details** tab, not the OAuth client ID).
+
+### `Not Authorized to access this resource/api` from Directory API
+
+- The impersonated `adminEmail` is missing the **User Management Admin** role. Promote them in Admin Console → Admin roles.
+- The Admin SDK API is not enabled on the Cloud project.
+
+### Users see a consent prompt every login
+
+- On the OAuth consent screen, ensure the app is **Published** (or **Internal** for Workspace-only apps) so it doesn't stay in testing mode.
+
+### `domain_mismatch`
+
+- The primary domain in the Workspace does not match the `domain` field. Use the primary domain, not an alias.
+
+---
+
+## Next Steps
+
+- [User Provisioning overview](./user-provisioning) — capabilities, attribute mappings, bulk import
+- [Role-Based Access Control](./rbac) — configure custom roles before mapping
+- [Audit Logs](./audit-logs) — track authentication events
diff --git a/docs/enterprise/setting-up-keycloak.mdx b/docs/enterprise/setting-up-keycloak.mdx
new file mode 100644
index 0000000000..b2fff165ba
--- /dev/null
+++ b/docs/enterprise/setting-up-keycloak.mdx
@@ -0,0 +1,237 @@
+---
+title: "Setting up Keycloak"
+description: "Step-by-step guide to configure a self-hosted Keycloak realm as your identity provider for Bifrost Enterprise SSO and user provisioning."
+icon: "lock"
+---
+
+## Overview
+
+This guide walks you through configuring [Keycloak](https://www.keycloak.org) as your identity provider for Bifrost Enterprise. Keycloak uses standard OIDC with JWKS-based JWT validation, and Bifrost uses the same client for both user login and Admin REST API access (via the Service Account of a confidential client).
+
+After completing this guide, users will sign in with their Keycloak credentials and admins can bulk-import users and groups via the Keycloak Admin REST API.
+
+## Prerequisites
+
+- A running Keycloak server (self-hosted or cloud) with admin access to a realm
+- Bifrost Enterprise deployed and accessible
+- The redirect URI for your Bifrost instance (e.g. `https://your-bifrost-domain.com/login`)
+- Bifrost [roles](./rbac) created for the roles you plan to map
+
+---
+
+## Step 1: Create a Client
+
+1. In the Keycloak Admin Console, select your realm and go to **Clients → Create client**.
+
+
+
+
+
+2. Configure the client:
+
+| Field | Value |
+| --- | --- |
+| **Client type** | OpenID Connect |
+| **Client ID** | `bifrost` (or your preferred identifier) |
+| **Name** | `Bifrost Enterprise` |
+
+3. On the **Capability config** step enable:
+ - **Client authentication** (makes it a confidential client)
+ - **Standard flow** (Authorization Code)
+ - **Service accounts roles** (required for Admin REST API access)
+
+
+
+
+
+4. On the **Login settings** step set:
+
+| Field | Value |
+| --- | --- |
+| **Valid redirect URIs** | `https://your-bifrost-domain.com/login` |
+| **Valid post logout redirect URIs** | `https://your-bifrost-domain.com` |
+| **Web origins** | `https://your-bifrost-domain.com` |
+
+5. **Save** the client.
+
+---
+
+## Step 2: Copy the client credentials
+
+1. Open the client and go to the **Credentials** tab.
+2. Copy the **Client Secret**.
+
+
+
+
+
+---
+
+## Step 3: Configure role and group mappers
+
+Keycloak does not include realm roles or full group paths in tokens by default. Add two mappers on the client's dedicated scope.
+
+1. Open the client → **Client Scopes** tab → click the client's `-dedicated` scope.
+2. Click **Add mapper → By configuration**.
+
+### Group Membership mapper
+
+
+
+
+
+| Field | Value |
+| --- | --- |
+| **Mapper Type** | Group Membership |
+| **Name** | `groups` |
+| **Token Claim Name** | `groups` |
+| **Full group path** | **On** |
+| **Add to ID token** | **On** |
+| **Add to access token** | **On** |
+| **Add to userinfo** | **On** |
+
+
+ Bifrost uses full group paths for consistent matching across SSO and bulk provisioning flows — keep **Full group path** enabled.
+
+
+### Realm Roles mapper
+
+| Field | Value |
+| --- | --- |
+| **Mapper Type** | User Realm Role |
+| **Name** | `realm_roles` |
+| **Token Claim Name** | `realm_access.roles` |
+| **Claim JSON Type** | String |
+| **Multivalued** | **On** |
+| **Add to ID token** | **On** |
+| **Add to access token** | **On** |
+
+---
+
+## Step 4: Assign Admin REST API permissions
+
+The same client runs both authentication and provisioning. Grant it read access to the realm so it can list users and groups.
+
+1. Open the client → **Service accounts roles** tab.
+2. Click **Assign role** and select:
+ - `realm-management` → **view-users** (required)
+ - `realm-management` → **view-realm** (recommended, enables group and role listing)
+ - `realm-management` → **query-groups** (optional, for group filters)
+
+
+
+
+
+---
+
+## Step 5: Create realm roles and groups
+
+Create the roles and groups you plan to map into Bifrost.
+
+1. **Realm → Realm roles → Create role** for each role (e.g. `bifrost-admin`, `bifrost-developer`, `bifrost-viewer`).
+2. **Realm → Groups → Create group** for each team you want to sync (e.g. `/platform`, `/data-science`).
+3. Assign users to the appropriate roles and groups under **Users → your user → Role mapping** / **Groups**.
+
+---
+
+## Step 6: Configure Bifrost
+
+### Using the Bifrost dashboard
+
+1. In Bifrost, go to **Governance → User Provisioning**.
+2. Select **Keycloak** as the SCIM Provider.
+3. Fill in the fields:
+
+| Field | Value |
+| --- | --- |
+| **Server URL** | `https://keycloak.company.com` (no `/realms/...` suffix) |
+| **Realm** | Your realm name (e.g. `master`, `bifrost-prod`) |
+| **Client ID** | Client ID from Step 1 |
+| **Client Secret** | Client Secret from Step 2 |
+| **Audience** | Optional — defaults to Client ID |
+| **Team IDs Field** | Leave as `groups` (default) or change if you used a different mapper name |
+
+4. Click **Verify** — Bifrost connects to Keycloak's JWKS and Admin REST API to confirm the client and service-account roles.
+5. Configure **Attribute → Role / Team / Business Unit** mappings if needed.
+6. Toggle **Enabled** and click **Save Configuration**.
+
+
+
+
+
+### Using `config.json`
+
+```json
+{
+ "scim_config": {
+ "enabled": true,
+ "provider": "keycloak",
+ "config": {
+ "serverUrl": "https://keycloak.company.com",
+ "realm": "bifrost-prod",
+ "clientId": "bifrost",
+ "clientSecret": "${KEYCLOAK_CLIENT_SECRET}",
+ "teamIdsField": "groups"
+ }
+ }
+}
+```
+
+### Configuration reference
+
+| Field | Required | Description |
+| --- | --- | --- |
+| `serverUrl` | Yes | Base URL of the Keycloak server. Must be a valid URL (e.g. `https://keycloak.company.com`) and must **not** include `/realms/...`. |
+| `realm` | Yes | Realm name. |
+| `clientId` | Yes | Client ID created in Step 1. |
+| `clientSecret` | Yes | Client secret — required because the client is confidential. |
+| `audience` | No | Expected JWT audience. Defaults to `clientId`. |
+| `teamIdsField` | No | JWT claim for group IDs. Defaults to `groups`. |
+| `attributeRoleMappings` | No | Ordered list of attribute→role mappings. |
+| `attributeTeamMappings` | No | Attribute→team mappings (all matches apply). |
+| `attributeBusinessUnitMappings` | No | Attribute→business-unit mappings (all matches apply). |
+
+---
+
+## Testing the Integration
+
+1. Open the Bifrost dashboard in an incognito window.
+2. You'll be redirected to Keycloak's login page.
+3. Sign in with a Keycloak user that has one of the roles you mapped.
+4. Verify the user appears under **Governance → Users** with the expected role and teams.
+5. From **Governance → User Provisioning → Import Users**, verify the service account can list users.
+
+---
+
+## Troubleshooting
+
+### `serverUrl must not include /realms/{realm}`
+
+The `serverUrl` field is the base Keycloak URL. Set the realm in the separate **Realm** field. Example: `https://keycloak.company.com` + realm `bifrost-prod` — **not** `https://keycloak.company.com/realms/bifrost-prod`.
+
+### Users redirected back to login
+
+- Confirm the client's **Valid redirect URIs** exactly match your Bifrost login URL (trailing slash matters).
+- Verify the client is **Enabled** in Keycloak.
+
+### Roles not appearing in the token
+
+- Check that the **User Realm Role** mapper adds to both ID and Access tokens.
+- Use `Evaluate` on the client scope to preview the token a user would receive.
+
+### Service account cannot list users
+
+- Confirm `realm-management → view-users` is assigned under **Service accounts roles**.
+- If you enabled **Authorization** on the client, service account tokens may not work — disable Authorization (fine-grained authz) for this client.
+
+### `jwks keys not found`
+
+- Make sure the server URL is reachable from Bifrost. The JWKS endpoint is `{serverUrl}/realms/{realm}/protocol/openid-connect/certs`.
+
+---
+
+## Next Steps
+
+- [User Provisioning overview](./user-provisioning) — capabilities, attribute mappings, bulk import
+- [Role-Based Access Control](./rbac) — configure custom roles before mapping
+- [Audit Logs](./audit-logs) — track authentication events
diff --git a/docs/enterprise/setting-up-okta.mdx b/docs/enterprise/setting-up-okta.mdx
index 6435459f7c..26ac5e8225 100644
--- a/docs/enterprise/setting-up-okta.mdx
+++ b/docs/enterprise/setting-up-okta.mdx
@@ -14,6 +14,7 @@ This guide walks you through configuring Okta as your identity provider for Bifr
- Bifrost Enterprise deployed and accessible
- The redirect URI for your Bifrost instance (e.g., `https://your-bifrost-domain.com/login`)
- Ensure you have created all the [roles in Bifrost](/enterprise/rbac) that you are aiming to map to with Okta.
+
---
## Step 1: Create an OIDC Application
@@ -47,20 +48,25 @@ Configure the following settings for your application:
**General Settings:**
+
- **App integration name**: `Bifrost Enterprise`
- **Logo** (optional): You can upload the Bifrost logo from [https://www.getmaxim.ai/bifrost/bifrost-logo-only.png](https://www.getmaxim.ai/bifrost/bifrost-logo-only.png)
**Grant type:**
+
- Enable **Authorization Code**
- Enable **Refresh Token**
**Sign-in redirect URIs:**
+
- Add your Bifrost login callback URL: `https://your-bifrost-domain.com/login`
**Sign-out redirect URIs (Optional):**
+
- Add your Bifrost base URL: `https://your-bifrost-domain.com`
**Assignments:**
+
- Choose **Skip group assignment for now** (we'll configure this later)
6. Click **Save** to create the application
@@ -71,10 +77,11 @@ Configure the following settings for your application:
---
-## Step 3: Create Custom Role Attribute
+## Step 3: Create Custom Role Attribute (Optional)
-You can use both roles and/or groups for assigning roles to users. You can learn more about [RBAC](/enterprise/rbac) docs. Roles take precedence over groups in role assignment.
+ You can map any attribute (include custom roles/groups) to assign roles to users. You can learn more about
+ [RBAC](/enterprise/rbac) docs.
To map Okta users to Bifrost roles (Admin, Developer, Viewer), you need to create a custom attribute.
@@ -93,20 +100,20 @@ To map Okta users to Bifrost roles (Admin, Developer, Viewer), you need to creat
-| Field | Value |
-|-------|-------|
-| **Data type** | string |
-| **Display name** | bifrostRole |
-| **Variable name** | bifrostRole |
-| **Enum** | Check "Define enumerated list of values" |
+| Field | Value |
+| --------------------- | ----------------------------------------------------------- |
+| **Data type** | string |
+| **Display name** | bifrostRole |
+| **Variable name** | bifrostRole |
+| **Enum** | Check "Define enumerated list of values" |
| **Attribute members** | Admin → `admin`, Developer → `developer`, Viewer → `viewer` |
-| **Attribute type** | Personal |
+| **Attribute type** | Personal |
5. Click **Save**
---
-## Step 4: Add Role Claim to Tokens
+## Step 4: Add Role Claim to Tokens (If you have added custom role attribute)
Configure the authorization server to include the role in the access token.
@@ -121,18 +128,19 @@ Configure the authorization server to include the role in the access token.
Configure the claim:
-| Field | Value |
-|-------|-------|
-| **Name** | `role` |
+| Field | Value |
+| ------------------------- | -------------------- |
+| **Name** | `role` |
| **Include in token type** | Access Token, Always |
-| **Value type** | Expression |
-| **Value** | `user.bifrostRole` |
-| **Include in** | Any scope |
+| **Value type** | Expression |
+| **Value** | `user.bifrostRole` |
+| **Include in** | Any scope |
5. Click **Create**
-If you named your custom attribute differently, update the Value expression accordingly (e.g., `user.yourAttributeName`).
+ If you named your custom attribute differently, update the Value expression accordingly (e.g.,
+ `user.yourAttributeName`).
---
@@ -140,6 +148,7 @@ If you named your custom attribute differently, update the Value expression acco
## Step 5: Configure Groups
Bifrost can automatically sync Okta groups for two purposes:
+
- **Team synchronization** — Groups are synced as Bifrost teams
- **Role mapping** — Groups can be mapped to Bifrost roles (Admin, Developer, Viewer) using Group-to-Role Mappings in the Bifrost UI.
@@ -159,7 +168,8 @@ Bifrost can automatically sync Okta groups for two purposes:
-Use a consistent naming convention for your groups. This makes it easier to configure group filters and role mappings later.
+ Use a consistent naming convention for your groups. This makes it easier to configure group filters and role mappings
+ later.
### Add Groups Claim to Tokens
@@ -173,13 +183,13 @@ This approach adds the groups claim through your authorization server, providing
Configure the groups claim:
-| Field | Value |
-|-------|-------|
-| **Name** | `groups` |
-| **Include in token type** | ID Token, Always |
-| **Value type** | Groups |
-| **Filter** | Matches regex: `.*` (or specify a prefix like `bifrost-.*`) |
-| **Include in** | Any scope |
+| Field | Value |
+| ------------------------- | ----------------------------------------------------------- |
+| **Name** | `groups` |
+| **Include in token type** | ID Token, Always |
+| **Value type** | Groups |
+| **Filter** | Matches regex: `.*` (or specify a prefix like `bifrost-.*`) |
+| **Include in** | Any scope |
5. Click **Create**
@@ -195,7 +205,7 @@ Configure the groups claim:
2. Click **Assign** → **Assign to People** or **Assign to Groups**
-### For Assigning Roles
+### For Assigning Roles (If step 3 and step 4 are followed)
For each user, set their **bifrostRole** (if you are planning to do role-level mapping):
@@ -205,10 +215,6 @@ For each user, set their **bifrostRole** (if you are planning to do role-level m
4. Click **Save and Go Back**
-
-Role claims are available only when you configure custom claims on your authorization server. Ensure you add role claims to your chosen authorization server (for example, `/oauth2/default`) to enable RBAC. If you skipped Steps 4-7, the first user to sign in automatically receives the **Admin** role and can manage RBAC for all subsequent users through the Bifrost dashboard.
-
-
---
## Step 7: Create API token for bulk user and team sync
@@ -216,7 +222,7 @@ Role claims are available only when you configure custom claims on your authoriz
To create an API token, navigate to **Security** → **API** → **Tokens**.
-
+
1. Click on "Create token"
@@ -233,60 +239,97 @@ Now configure Bifrost to use Okta as the identity provider.
### Using the Bifrost UI
+
+
+
+
1. Navigate to **Governance** → **User Provisioning** in your Bifrost dashboard
2. Select **Okta** as the SCIM Provider
3. Enter the following configuration:
-| Field | Value |
-|-------|-------|
-| **Client ID** | Your Okta application Client ID |
-| **Issuer URL** | Issuer URL |
-| **Audience** | Your API audience (e.g., `api://default` or custom) |
+| Field | Value |
+| ----------------- | -------------------------------------------------------------------- |
+| **Client ID** | Your Okta application Client ID |
+| **Issuer URL** | Issuer URL |
+| **Audience** | Your API audience (e.g., `api://default` or custom) |
| **Client Secret** | Your Okta application Client Secret (optional, for token revocation) |
-4. Toggle **Enabled** to activate the provider
-5. Click **Save Configuration**
+4. **Verify** configuration and see if you get any errors. Make sure you get no errors/warnings.
+5. Toggle **Enabled** to activate the provider
+6. Click **Save Configuration**
+
+
+After saving, you'll need to restart your Bifrost server for the changes to take effect.
+
+
+### Attribute Mappings
+
+Attribute mappings let you translate Okta claim values into Bifrost roles, teams, or business units without restructuring your Okta claims. Bifrost supports three mapping types:
-### Group-to-Role Mappings
+- **`attributeRoleMappings`**: map a claim value to a Bifrost role (Admin, Developer, Viewer, or a custom role)
+- **`attributeTeamMappings`**: map a claim value to a Bifrost team
+- **`attributeBusinessUnitMappings`**: map a claim value to a Bifrost business unit
-If you configured groups in Okta (Step 5), you can map Okta group names directly to Bifrost roles. This is an alternative to using custom role claims (Steps 3-4) and works with all Okta plans.
+These mappings work with any Okta claim — the `groups` claim from Step 5, the custom `role` claim from Step 4, or any other claim your authorization server includes in the token (e.g., `department`, `organization`).
-1. In the User Provisioning configuration, scroll down to **Group-to-Role Mappings**
-2. Click **Add Mapping**
-3. Enter the **Group Claim Name** exactly as it appears in your Okta groups (e.g., `bifrost-staging-admins`)
-4. Select the corresponding **Role** (Admin, Developer, or Viewer)
-5. Repeat for each group you want to map
+To configure attribute mappings:
+
+1. In the User Provisioning configuration, scroll down to **Attribute Mappings**
+2. Click **Add Mapping** under the relevant mapping type (Role, Team, or Business Unit)
+3. Enter the **Attribute** (the claim name from the token), the **Value** to match, and the target **Role**, **Team**, or **Business Unit**
+4. Repeat for each rule you need
-
+
-| Group Claim Name | Role |
-|------------------|------|
-| `bifrost-staging-admins` | Admin |
-| `bifrost-staging-viewers` | Viewer |
+
+ When you mark value as "*" - the claim value is mapped as is to the entity name. Values comparisons are case-insensitive.
+
+
+### Custom attribute mapping
+
+You can also map any custom attributes to any entity (role, team or business unit). Make sure these are configured to send back to Bifrost in token configuration.
+
+
+
+
+
+
+#### Evaluation rules
+
+- **Role mappings**: Ordered, first match wins. If no rule matches, users are not allowed to login into the system.
+- **Team and business unit mappings**: All matching rules apply — users can be placed on multiple teams and business units simultaneously.
+- **Claim values**: Can be strings, arrays, or nested objects. Bifrost resolves dotted paths (e.g., `realm_access.roles`).
-If a user belongs to multiple groups with different role mappings, the highest privilege role is assigned. If no mapping matches, the first user to sign in receives the **Admin** role, and subsequent users receive the **Viewer** role.
+ If a user matches multiple role mapping rules, the highest privilege role is assigned. If no
+ mapping matches, the first user to sign in receives the **Admin** role, and subsequent users receive the **Viewer**
+ role.
-6. Click **Save Configuration**
+5. Click **Save Configuration**
-
-After saving, you'll need to restart your Bifrost server for the changes to take effect.
-
+After saving, you'll need to restart your Bifrost server for the changes to take effect.
### Configuration Reference
-| Field | Required | Description |
-|-------|----------|-------------|
-| `issuerUrl` | Yes | Okta authorization server URL (e.g., `https://your-domain.okta.com/oauth2/default`) |
-| `clientId` | Yes | Application Client ID from Okta |
-| `clientSecret` | No | Application Client Secret (enables token revocation) |
-| `audience` | Yes | API audience identifier from your authorization server |
-| `userIdField` | No | JWT claim for user ID (default: `uid`) |
-| `rolesField` | No | JWT claim for roles (default: `roles`) |
-| `teamIdsField` | No | JWT claim for group/team IDs (default: `groups`) |
+| Field | Required | Description |
+| ------------------------------- | -------- | ----------------------------------------------------------------------------------- |
+| `issuerUrl` | Yes | Okta authorization server URL (e.g., `https://your-domain.okta.com/oauth2/default`) |
+| `clientId` | Yes | Application Client ID from Okta |
+| `clientSecret` | Yes | Application Client Secret (enables token revocation) |
+| `audience` | Yes | API audience identifier from your authorization server |
+| `attributeRoleMappings` | Yes | Ordered list of attribute→role mappings. First match wins. |
+| `attributeTeamMappings` | No | Attribute→team mappings (all matches apply). |
+| `attributeBusinessUnitMappings` | No | Attribute→business-unit mappings (all matches apply). |
+
---
@@ -308,22 +351,10 @@ After saving, you'll need to restart your Bifrost server for the changes to take
- Check that the Bifrost server was restarted after configuration
- Ensure the Issuer URL is correct and accessible
-### "Invalid audience" error
-
-- Verify the `audience` field matches your Okta authorization server's audience
-- Check if you're using a custom authorization server and update the issuer URL accordingly
-
-### Roles not syncing
-- Ensure the `role` claim is configured in your authorization server
-- Verify users have the `bifrostRole` attribute set
-- Check that the claim is included in the access token (use Okta's Token Preview feature)
+### Attribute mapping is not working
-### Groups not appearing as teams
-
-- Verify the `groups` claim is configured in your authorization server
-- Ensure users are assigned to the relevant groups
-- Check that groups are assigned to the application
+- Verify that token configuration includes all the attributes used for mapping.
### Token refresh failing
@@ -334,7 +365,7 @@ After saving, you'll need to restart your Bifrost server for the changes to take
## Next Steps
+- **[User Provisioning (SCIM)](./user-provisioning)** - Overview of SCIM in Bifrost and alternative identity providers
- **[Advanced Governance](./advanced-governance)** - Learn about user budgets and compliance features
- **[Role-Based Access Control](./advanced-governance#role-hierarchy)** - Understand the Admin, Developer, Viewer hierarchy
- **[Audit Logs](./audit-logs)** - Monitor user authentication and activity
-
diff --git a/docs/enterprise/setting-up-zitadel.mdx b/docs/enterprise/setting-up-zitadel.mdx
new file mode 100644
index 0000000000..03fcffe81e
--- /dev/null
+++ b/docs/enterprise/setting-up-zitadel.mdx
@@ -0,0 +1,246 @@
+---
+title: "Setting up Zitadel"
+description: "Step-by-step guide to configure Zitadel (cloud or self-hosted) as your identity provider for Bifrost Enterprise SSO and user provisioning."
+icon: "cloud"
+---
+
+## Overview
+
+This guide walks you through configuring [Zitadel](https://zitadel.com) as your identity provider for Bifrost Enterprise. Zitadel uses standard OIDC with JWKS-based JWT validation, plus a separate **service account** for user provisioning (Zitadel web applications cannot perform the `client_credentials` grant — a dedicated service account is required for directory-level reads).
+
+After completing this guide users will sign in to Bifrost with their Zitadel credentials, and admins can bulk-import users and teams from the Zitadel Management API.
+
+## Prerequisites
+
+- A Zitadel instance (cloud at `*.zitadel.cloud` or self-hosted) with admin access
+- An existing Zitadel **Project** in the organization you want to connect
+- Bifrost Enterprise deployed and accessible
+- The redirect URI for your Bifrost instance (e.g. `https://your-bifrost-domain.com/login`)
+- Bifrost [roles](./rbac) created for the roles you plan to map (Admin, Developer, Viewer, or custom)
+
+---
+
+## Step 1: Create a Web Application
+
+The Web Application is what end users log in through.
+
+1. Open the Zitadel Console and choose **Projects → your project → New Application**.
+
+
+
+
+
+
+2. Configure the application:
+
+| Field | Value |
+| --- | --- |
+| **Type** | Web |
+| **Authentication method** | PKCE (recommended) or Basic |
+| **Redirect URI** | `https://your-bifrost-domain.com/login` |
+| **Post logout URI** | `https://your-bifrost-domain.com` |
+
+3. After creating the app, note the **Client ID** from the application detail page.
+
+
+
+
+
+4. If you chose a confidential authentication method, also copy the **Client Secret** (shown once).
+
+---
+
+## Step 2: Enable role claims on the project (Optional)
+
+
+ In Bifrost, you can map any attribute to role. If you decide to map Zitadel project roles, then follow step 2 and step 3.
+
+
+Zitadel only emits role claims in access tokens when the project is configured to assert them.
+
+1. Open **Projects → your project → General**.
+2. Enable **Assert Roles on Authentication**.
+3. Enable **Check Authorization on Authentication** if you want to enforce that every user has at least one project role.
+
+
+
+
+
+4. Note the **Project ID** — you'll need it for the Bifrost config so Bifrost can resolve the correct project roles.
+
+
+ Without **Assert Roles on Authentication**, the token will not contain role claims and every user will fall back to the default role (Viewer, or Admin for the first signin).
+
+
+---
+
+## Step 3: Create project roles (Optional)
+
+
+
+
+
+
+1. In the same project, open the **Roles** tab and create a role for each Bifrost role you plan to map. Common pattern:
+2. Authorize users to the relevant roles via **Users → Roles**.
+
+
+
+
+
+
+---
+
+## Step 4: Create a service account for provisioning
+
+Web apps in Zitadel cannot use the `client_credentials` grant. Bifrost needs a dedicated service account to list users via the Management API.
+
+1. Navigate to **Users → Service Accounts → New**.
+
+
+
+
+
+2. Name it (e.g. `bifrost-provisioning`) and create it.
+3. Open the service account → **Actions → Generate Client Secret**.
+
+
+
+
+
+4. **Copy the Client ID and Client Secret immediately** — the secret is shown only once.
+
+
+ Store the service account Client Secret in your password manager. It cannot be retrieved after this screen.
+
+
+5. Grant the service account **IAM_USER_READ** (or **Org Owner** if you want broader visibility):
+ - Organization → **Managers → Add Manager** → select the service account → role `IAM_USER_READ`.
+
+
+
+
+
+---
+
+## Step 5: App token settings
+
+1. Change **Auth Token Type** to JWT.
+2. Enable roles and profile info in ID token.
+
+
+
+
+
+
+## Step 6: Configure Bifrost
+
+### Using the Bifrost dashboard
+
+
+1. In Bifrost, go to **Governance → User Provisioning**.
+2. Select **Zitadel** as the SCIM Provider.
+3. Fill in the fields:
+
+| Field | Value |
+| --- | --- |
+| **Domain** | Your Zitadel host, e.g. `my-instance.zitadel.cloud` or `auth.company.com` (no scheme, no path) |
+| **Project ID** | The project ID from Step 2 |
+| **Client ID** | Web Application Client ID from Step 1 |
+| **Client Secret** | Web Application Client Secret from Step 1 (optional for PKCE) |
+| **Audience** | Optional access-token audience override |
+| **Service Account Client ID** | From Step 4 |
+| **Service Account Client Secret** | From Step 4 |
+
+4. Click **Verify** — Bifrost connects to Zitadel's JWKS and service account token endpoints to confirm the credentials.
+5. Configure **Attribute → Role / Team / Business Unit** mappings if you need to translate project roles or metadata into Bifrost roles.
+6. Toggle **Enabled** and click **Save Configuration**.
+
+
+
+
+
+### Using `config.json`
+
+```json
+{
+ "scim_config": {
+ "enabled": true,
+ "provider": "zitadel",
+ "config": {
+ "domain": "my-instance.zitadel.cloud",
+ "projectId": "123456789012345678",
+ "clientId": "123456789012345678@my-project",
+ "clientSecret": "${ZITADEL_CLIENT_SECRET}",
+ "serviceAccountClientId": "987654321098765432@my-project",
+ "serviceAccountClientSecret": "${ZITADEL_SA_CLIENT_SECRET}",
+ "teamIdsField": "groups"
+ }
+ }
+}
+```
+
+### Custom attribute mapping
+
+You can also map any custom attributes to any entity (role, team or business unit). Make sure these are configured to send back to Bifrost in token configuration.
+
+
+
+
+
+### Configuration reference
+
+| Field | Required | Description |
+| --- | --- | --- |
+| `domain` | Yes | Zitadel instance host (no scheme). Examples: `my-instance.zitadel.cloud`, `auth.company.com`. |
+| `clientId` | Yes | Client ID of the Web Application used for user login. |
+| `clientSecret` | Yes | Web Application secret. Omit for PKCE-only flows. |
+| `projectId` | Yes | Required to resolve project-scoped role claims and sync role grants. |
+| `audience` | No | Override the expected JWT `aud` claim. |
+| `serviceAccountClientId` | Yes | Service account used to list users via the Management API. |
+| `serviceAccountClientSecret` | Yes | Service account secret (shown once in Zitadel). |
+| `attributeRoleMappings` | Yes | Ordered list of attribute→role mappings. |
+| `attributeTeamMappings` | No | Attribute→team mappings (all matches apply). |
+| `attributeBusinessUnitMappings` | No | Attribute→business-unit mappings (all matches apply). |
+
+---
+
+## Testing the Integration
+
+1. Open the Bifrost dashboard in an incognito window.
+2. You'll be redirected to Zitadel. Sign in with a user who has a project authorization.
+3. On successful login you return to Bifrost and appear in **Governance → Users** with the correct role.
+4. From **Governance → User Provisioning → Import Users**, verify you can preview and import additional users via the service account.
+
+---
+
+## Troubleshooting
+
+### `role claims missing in token`
+
+- Enable **Assert Roles on Authentication** on the project (Step 2).
+- Ensure the user has an active authorization for the project.
+
+### `invalid audience` when validating the JWT
+
+- Check the `audience` field in the Bifrost config. It must match the `aud` claim issued by Zitadel. Leaving it empty uses the default (the project's resource owner).
+
+### Service account cannot list users
+
+- Confirm the service account has **IAM_USER_READ** or **Org Owner** role in the organization.
+- Regenerate the client secret if you've lost it — the original secret cannot be retrieved.
+
+### Redirect URI mismatch
+
+- Zitadel requires an exact string match. Check for trailing slashes and `http` vs `https`.
+
+---
+
+## Next Steps
+
+- [User Provisioning overview](./user-provisioning) — capabilities, attribute mappings, bulk import
+- [Role-Based Access Control](./rbac) — configure custom roles before mapping
+- [Audit Logs](./audit-logs) — track authentication events
diff --git a/docs/enterprise/user-provisioning.mdx b/docs/enterprise/user-provisioning.mdx
new file mode 100644
index 0000000000..78eaa8e872
--- /dev/null
+++ b/docs/enterprise/user-provisioning.mdx
@@ -0,0 +1,200 @@
+---
+title: "User Provisioning (SCIM)"
+description: "Authenticate users, sync teams, and provision roles and business units from your identity provider using SCIM-backed OAuth 2.0 / OIDC flows."
+icon: "users-gear"
+---
+
+## Overview
+
+Bifrost Enterprise uses **SCIM-backed identity provisioning** to connect your organization's identity provider to Bifrost. A single configuration gives you:
+
+- **Single sign-on (SSO)** via OAuth 2.0 / OIDC with JWKS-based JWT validation
+- **Automatic role assignment** using custom claims, app roles, or group-to-role mappings
+- **Team synchronization** from IdP groups into Bifrost teams
+- **Business unit mapping** from IdP attributes to Bifrost business units
+- **Bulk user provisioning** with filter-preview before import
+- **Silent token refresh** using server-stored refresh tokens
+
+Once configured, users sign in to Bifrost with their corporate credentials and inherit the right [role and permissions](./rbac) immediately — no manual account creation.
+
+
+
+
+
+---
+
+## Supported Identity Providers
+
+Pick your IdP to follow a step-by-step setup guide. All providers share the same Bifrost configuration surface — the only difference is how the OAuth client and role/group claims are created on the provider side.
+
+
+
+ OIDC with Org or Custom Authorization Servers, plus group-to-role mapping and API tokens for bulk user sync.
+
+
+ Entra ID (Azure AD) with app roles, group claims, and v1.0 / v2.0 token support.
+
+
+ Cloud or self-hosted Zitadel with project-scoped role claims and service-account-based provisioning.
+
+
+ Self-hosted Keycloak with realm roles, group path claims, and Admin REST API access.
+
+
+ Google Workspace domains with OAuth login plus optional Directory API sync via a service account.
+
+
+
+---
+
+## How it works
+
+
+
+
+
+1. **Login** — Bifrost redirects unauthenticated users to the provider's authorization endpoint (Authorization Code flow).
+2. **Token exchange** — on callback, Bifrost exchanges the code for an access token and refresh token, stores them in an `HttpOnly` cookie / server session, and validates the JWT against the provider's JWKS.
+3. **Identity extraction** — configurable JWT claims (`userIdField`, `rolesField`, `teamIdsField`) are mapped to a Bifrost user, role, and teams. Provider-specific app roles or custom attributes override claim lookup.
+4. **Attribute mapping** — optional `attributeRoleMappings`, `attributeTeamMappings`, and `attributeBusinessUnitMappings` translate arbitrary claim values (e.g., a department string or Okta group name) into Bifrost roles, teams, or business units.
+5. **Bulk import** — admins can preview users matching a filter and bulk-import them via the dashboard, which calls the provider's user directory API.
+6. **Silent refresh** — when the access token expires, Bifrost uses the stored refresh token to mint a new one without requiring re-login.
+
+---
+
+## Capabilities
+
+| Capability | Description |
+| --- | --- |
+| **OAuth 2.0 / OIDC SSO** | Authorization Code + PKCE with configurable scopes (`openid profile email offline_access`). |
+| **JWKS validation** | JWTs are validated against the provider's published JWKS keys; configuration is cached and auto-refreshed. |
+| **Role mapping** | Map from a claim value (string or array) to Admin / Developer / Viewer or a custom role. Highest-privilege wins when multiple match. |
+| **Team mapping** | Map multiple claim values to Bifrost teams in a single pass (a user can belong to many teams). |
+| **Business unit mapping** | Same as team mapping but scoped to business units. |
+| **Provisioning preview** | Preview up to 50 users matching filters (groups, roles, departments) before importing. |
+| **Bulk import** | Import matched users into Bifrost with role + team + BU assignments applied. |
+| **Team sync** | Sync IdP groups as Bifrost teams with a single action. |
+| **Business unit sync** | Sync IdP organizational units as Bifrost business units. |
+| **Deprovisioning** | Re-running import reconciles removed users and updates role / team assignments. |
+| **API key pass-through** | Requests using Bifrost API keys (`bfst-*`) bypass SCIM middleware so inference traffic is not affected. |
+
+---
+
+## Configuration reference
+
+All providers share the same outer config shape in `config.json`:
+
+```json
+{
+ "scim_config": {
+ "enabled": true,
+ "provider": "okta | entra | zitadel | keycloak | google",
+ "config": {
+ "...": "provider-specific fields — see each IdP guide"
+ }
+ }
+}
+```
+
+Shared fields across providers:
+
+| Field | Required | Description |
+| --- | --- | --- |
+| `clientId` | Yes | OAuth client ID from the identity provider. |
+| `clientSecret` | Usually | Client secret. Required for confidential clients and (where applicable) token revocation. |
+| `audience` | Optional | JWT audience to validate against. Defaults vary per provider. |
+| `attributeRoleMappings` | Optional | Ordered list of `{ attribute, value, role }` rules evaluated top-to-bottom. |
+| `attributeTeamMappings` | Optional | List of `{ attribute, value, team }` rules (all matches apply). |
+| `attributeBusinessUnitMappings` | Optional | List of `{ attribute, value, businessUnit }` rules (all matches apply). |
+
+Provider-specific fields (domain, tenant ID, server URL, service-account credentials) are documented in each IdP's setup guide.
+
+
+ Changing `scim_config` at runtime through the UI is applied after saving. For file-based configuration, restart the Bifrost server to pick up changes.
+
+
+---
+
+## Configuring from the dashboard
+
+1. Navigate to **Governance → User Provisioning** in the Bifrost dashboard.
+2. Select your identity provider from the **SCIM Provider** dropdown.
+3. Fill in the provider-specific fields. Required fields are marked and validated on **Verify**.
+
+
+
+
+
+4. Click **Verify** to test credentials end-to-end. Bifrost will reach the provider's JWKS / directory endpoint and report any failures.
+5. Configure **Attribute → Role / Team / Business Unit** mappings as needed.
+6. Toggle **Enabled** and click **Save Configuration**.
+
+
+ After enabling a new provider, the next dashboard load redirects to your IdP for login. Test in an incognito window first to avoid being locked out of your current session.
+
+
+---
+
+## Attribute mappings
+
+Attribute mappings let you translate claim values into Bifrost roles, teams, or business units without forcing your IdP admins to restructure claim names.
+
+
+
+
+
+Each mapping is an ordered rule:
+
+```json
+{
+ "attribute": "department",
+ "value": "Engineering",
+ "role": "developer"
+}
+```
+
+Rules are evaluated top-to-bottom:
+- **Role mappings** — first match wins. Set a fallback with `"attribute": "*"` at the end.
+- **Team mappings** and **business unit mappings** — all matching rules apply, so a user with `department=Platform` and `group=sre` can be placed on multiple teams.
+
+Claim values can be strings, arrays, or nested objects — Bifrost resolves dotted paths (e.g., `realm_access.roles`).
+
+---
+
+## Bulk user provisioning
+
+Once SCIM is enabled, import users in bulk from your IdP:
+
+1. Go to **Governance → User Provisioning → Import Users**.
+2. Select a filter — groups, roles, departments, or a custom query depending on provider support.
+3. Click **Preview** to see up to 50 matching users.
+4. Click **Import** to create them in Bifrost with role / team / BU assignments applied.
+
+
+
+
+
+Re-running an import reconciles existing users — role and team changes in the IdP are reflected on the next import.
+
+---
+
+## Troubleshooting
+
+| Symptom | Likely cause |
+| --- | --- |
+| Access denied: no application role or group mapping is assigned to this user. | Make sure you have assigned user to the Bifrost IdP application and they have a valid group/attribute mapping to role in Bifrost |
+| Redirect loop on login | Make sure you have restarted pods/Bifrost instance after changing SCIM configuration, or check for a redirect URI mismatch. Exact string match required — check trailing slashes and `http` vs `https`. |
+| `invalid audience` | `audience` field does not match the access token's `aud` claim. Use the same value your IdP issues. |
+| Empty roles / teams | Claim mapping is off. Verify the JWT at [jwt.io](https://jwt.io) and check `rolesField` / `teamIdsField`. |
+| Token refresh failing | `offline_access` scope missing or refresh token revoked. Re-enable the scope and re-authenticate. |
+| First user gets Admin | By design — if no matching role mapping applies, the first user is promoted to Admin so they can finish configuration. Subsequent users default to Viewer. |
+
+Provider-specific troubleshooting lives in each IdP's guide.
+
+---
+
+## Related
+
+- [Role-Based Access Control](./rbac) — permissions model and custom roles
+- [Advanced Governance](./advanced-governance) — budgets, limits, and compliance
+- [Audit Logs](./audit-logs) — track authentication events and role changes
diff --git a/docs/features/governance/budget-and-limits.mdx b/docs/features/governance/budget-and-limits.mdx
index 1a012b7904..b295565e72 100644
--- a/docs/features/governance/budget-and-limits.mdx
+++ b/docs/features/governance/budget-and-limits.mdx
@@ -345,59 +345,78 @@ Configure provider-level governance through Bifrost's configuration file for dec
"governance": {
"virtual_keys": [
{
+ "id": "vk-dev-001",
"name": "development-team-vk",
"description": "Development team with multi-provider setup",
+ "is_active": true,
+ "rate_limit_id": "rl-vk-dev",
"provider_configs": [
{
+ "id": 1,
"provider": "openai",
"weight": 0.6,
"allowed_models": ["gpt-4", "gpt-3.5-turbo"],
- "budget": {
- "max_limit": 1000.00,
- "reset_duration": "1M"
- },
- "rate_limit": {
- "token_max_limit": 2000000,
- "token_reset_duration": "1h",
- "request_max_limit": 2000,
- "request_reset_duration": "1h"
- }
+ "rate_limit_id": "rl-pc-openai"
},
{
+ "id": 2,
"provider": "anthropic",
"weight": 0.4,
"allowed_models": ["claude-3-opus", "claude-3-sonnet"],
- "budget": {
- "max_limit": 500.00,
- "reset_duration": "1M"
- },
- "rate_limit": {
- "token_max_limit": 1000000,
- "token_reset_duration": "1h",
- "request_max_limit": 1000,
- "request_reset_duration": "1h"
- }
+ "rate_limit_id": "rl-pc-anthropic"
}
- ],
- "budget": {
- "max_limit": 2000.00,
- "reset_duration": "1M",
- "calendar_aligned": true
- },
- "rate_limit": {
- "token_max_limit": 5000000,
- "token_reset_duration": "1h",
- "request_max_limit": 3000,
- "request_reset_duration": "1h"
- },
- "is_active": true
+ ]
+ }
+ ],
+ "budgets": [
+ {
+ "id": "budget-vk-dev",
+ "virtual_key_id": "vk-dev-001",
+ "max_limit": 2000.00,
+ "reset_duration": "1M",
+ "calendar_aligned": true
+ },
+ {
+ "id": "budget-pc-openai",
+ "provider_config_id": 1,
+ "max_limit": 1000.00,
+ "reset_duration": "1M"
+ },
+ {
+ "id": "budget-pc-anthropic",
+ "provider_config_id": 2,
+ "max_limit": 500.00,
+ "reset_duration": "1M"
+ }
+ ],
+ "rate_limits": [
+ {
+ "id": "rl-vk-dev",
+ "token_max_limit": 5000000,
+ "token_reset_duration": "1h",
+ "request_max_limit": 3000,
+ "request_reset_duration": "1h"
+ },
+ {
+ "id": "rl-pc-openai",
+ "token_max_limit": 2000000,
+ "token_reset_duration": "1h",
+ "request_max_limit": 2000,
+ "request_reset_duration": "1h"
+ },
+ {
+ "id": "rl-pc-anthropic",
+ "token_max_limit": 1000000,
+ "token_reset_duration": "1h",
+ "request_max_limit": 1000,
+ "request_reset_duration": "1h"
}
]
}
}
```
-Optional `calendar_aligned` on each `budget` matches the HTTP API and [calendar-aligned behavior](#calendar-aligned-budgets).
+Budgets and rate limits live as **separate top-level arrays** inside `governance`. Virtual keys and provider configs reference them by id (`rate_limit_id`) or are referenced back (`virtual_key_id` / `provider_config_id` on each `budgets[]` entry). Optional `calendar_aligned` on each `budget` matches the HTTP API and [calendar-aligned behavior](#calendar-aligned-budgets).
### Advanced Configuration Examples
@@ -407,34 +426,21 @@ Optional `calendar_aligned` on each `budget` matches the HTTP API and [calendar-
"governance": {
"virtual_keys": [
{
+ "id": "vk-cost-opt",
"name": "cost-optimized-vk",
"provider_configs": [
- {
- "provider": "openai-gpt-3.5",
- "weight": 0.8,
- "budget": {
- "max_limit": 50.00,
- "reset_duration": "1d"
- },
- "rate_limit": {
- "request_max_limit": 1000,
- "request_reset_duration": "1h"
- }
- },
- {
- "provider": "openai-gpt-4",
- "weight": 0.2,
- "budget": {
- "max_limit": 200.00,
- "reset_duration": "1d"
- },
- "rate_limit": {
- "request_max_limit": 100,
- "request_reset_duration": "1h"
- }
- }
+ {"id": 10, "provider": "openai-gpt-3.5", "weight": 0.8, "rate_limit_id": "rl-cheap"},
+ {"id": 11, "provider": "openai-gpt-4", "weight": 0.2, "rate_limit_id": "rl-premium"}
]
}
+ ],
+ "budgets": [
+ {"id": "b-cheap", "provider_config_id": 10, "max_limit": 50.00, "reset_duration": "1d"},
+ {"id": "b-premium", "provider_config_id": 11, "max_limit": 200.00, "reset_duration": "1d"}
+ ],
+ "rate_limits": [
+ {"id": "rl-cheap", "request_max_limit": 1000, "request_reset_duration": "1h"},
+ {"id": "rl-premium", "request_max_limit": 100, "request_reset_duration": "1h"}
]
}
}
@@ -446,52 +452,24 @@ Optional `calendar_aligned` on each `budget` matches the HTTP API and [calendar-
"governance": {
"virtual_keys": [
{
+ "id": "vk-prod-hv",
"name": "production-high-volume-vk",
"provider_configs": [
- {
- "provider": "openai",
- "weight": 0.5,
- "budget": {
- "max_limit": 5000.00,
- "reset_duration": "1M"
- },
- "rate_limit": {
- "token_max_limit": 10000000,
- "token_reset_duration": "1h",
- "request_max_limit": 10000,
- "request_reset_duration": "1h"
- }
- },
- {
- "provider": "anthropic",
- "weight": 0.3,
- "budget": {
- "max_limit": 3000.00,
- "reset_duration": "1M"
- },
- "rate_limit": {
- "token_max_limit": 6000000,
- "token_reset_duration": "1h",
- "request_max_limit": 6000,
- "request_reset_duration": "1h"
- }
- },
- {
- "provider": "azure-openai",
- "weight": 0.2,
- "budget": {
- "max_limit": 2000.00,
- "reset_duration": "1M"
- },
- "rate_limit": {
- "token_max_limit": 4000000,
- "token_reset_duration": "1h",
- "request_max_limit": 4000,
- "request_reset_duration": "1h"
- }
- }
+ {"id": 20, "provider": "openai", "weight": 0.5, "rate_limit_id": "rl-openai"},
+ {"id": 21, "provider": "anthropic", "weight": 0.3, "rate_limit_id": "rl-anthropic"},
+ {"id": 22, "provider": "azure-openai", "weight": 0.2, "rate_limit_id": "rl-azure"}
]
}
+ ],
+ "budgets": [
+ {"id": "b-openai", "provider_config_id": 20, "max_limit": 5000.00, "reset_duration": "1M"},
+ {"id": "b-anthropic", "provider_config_id": 21, "max_limit": 3000.00, "reset_duration": "1M"},
+ {"id": "b-azure", "provider_config_id": 22, "max_limit": 2000.00, "reset_duration": "1M"}
+ ],
+ "rate_limits": [
+ {"id": "rl-openai", "token_max_limit": 10000000, "token_reset_duration": "1h", "request_max_limit": 10000, "request_reset_duration": "1h"},
+ {"id": "rl-anthropic", "token_max_limit": 6000000, "token_reset_duration": "1h", "request_max_limit": 6000, "request_reset_duration": "1h"},
+ {"id": "rl-azure", "token_max_limit": 4000000, "token_reset_duration": "1h", "request_max_limit": 4000, "request_reset_duration": "1h"}
]
}
}
@@ -514,20 +492,23 @@ A virtual key configured with multiple providers and different budget allocation
```json
{
- "name": "marketing-team-vk",
- "budget": { "max_limit": 100, "reset_duration": "1M" },
- "provider_configs": [
- {
- "provider": "openai",
- "weight": 0.7,
- "budget": { "max_limit": 50, "reset_duration": "1M" }
- },
- {
- "provider": "anthropic",
- "weight": 0.3,
- "budget": { "max_limit": 30, "reset_duration": "1M" }
- }
- ]
+ "governance": {
+ "virtual_keys": [
+ {
+ "id": "vk-mkt",
+ "name": "marketing-team-vk",
+ "provider_configs": [
+ {"id": 30, "provider": "openai", "weight": 0.7},
+ {"id": 31, "provider": "anthropic", "weight": 0.3}
+ ]
+ }
+ ],
+ "budgets": [
+ {"id": "b-vk-mkt", "virtual_key_id": "vk-mkt", "max_limit": 100, "reset_duration": "1M"},
+ {"id": "b-openai", "provider_config_id": 30, "max_limit": 50, "reset_duration": "1M"},
+ {"id": "b-anth", "provider_config_id": 31, "max_limit": 30, "reset_duration": "1M"}
+ ]
+ }
}
```
@@ -542,27 +523,22 @@ Different rate limits based on provider capabilities:
```json
{
- "name": "high-volume-vk",
- "provider_configs": [
- {
- "provider": "openai",
- "rate_limit": {
- "request_max_limit": 1000,
- "request_reset_duration": "1h",
- "token_max_limit": 1000000,
- "token_reset_duration": "1h"
- }
- },
- {
- "provider": "anthropic",
- "rate_limit": {
- "request_max_limit": 500,
- "request_reset_duration": "1h",
- "token_max_limit": 500000,
- "token_reset_duration": "1h"
+ "governance": {
+ "virtual_keys": [
+ {
+ "id": "vk-hv",
+ "name": "high-volume-vk",
+ "provider_configs": [
+ {"id": 40, "provider": "openai", "rate_limit_id": "rl-openai"},
+ {"id": 41, "provider": "anthropic", "rate_limit_id": "rl-anthropic"}
+ ]
}
- }
- ]
+ ],
+ "rate_limits": [
+ {"id": "rl-openai", "request_max_limit": 1000, "request_reset_duration": "1h", "token_max_limit": 1000000, "token_reset_duration": "1h"},
+ {"id": "rl-anthropic", "request_max_limit": 500, "request_reset_duration": "1h", "token_max_limit": 500000, "token_reset_duration": "1h"}
+ ]
+ }
}
```
@@ -577,25 +553,25 @@ Provider configurations with budget-based failover:
```json
{
- "name": "cost-optimized-vk",
- "provider_configs": [
- {
- "provider": "openai-cheap",
- "weight": 1.0,
- "budget": { "max_limit": 10, "reset_duration": "1d" }
- },
- {
- "provider": "openai-premium",
- "weight": 0.0,
- "budget": { "max_limit": 50, "reset_duration": "1d" },
- "rate_limit": {
- "request_max_limit": 100,
- "request_reset_duration": "1h",
- "token_max_limit": 50000,
- "token_reset_duration": "1h"
+ "governance": {
+ "virtual_keys": [
+ {
+ "id": "vk-cost",
+ "name": "cost-optimized-vk",
+ "provider_configs": [
+ {"id": 50, "provider": "openai-cheap", "weight": 1.0},
+ {"id": 51, "provider": "openai-premium", "weight": 0.0, "rate_limit_id": "rl-premium"}
+ ]
}
- }
- ]
+ ],
+ "budgets": [
+ {"id": "b-cheap", "provider_config_id": 50, "max_limit": 10, "reset_duration": "1d"},
+ {"id": "b-premium", "provider_config_id": 51, "max_limit": 50, "reset_duration": "1d"}
+ ],
+ "rate_limits": [
+ {"id": "rl-premium", "request_max_limit": 100, "request_reset_duration": "1h", "token_max_limit": 50000, "token_reset_duration": "1h"}
+ ]
+ }
}
```
diff --git a/docs/features/governance/virtual-keys.mdx b/docs/features/governance/virtual-keys.mdx
index 8ae28e42ce..6c8cff9320 100644
--- a/docs/features/governance/virtual-keys.mdx
+++ b/docs/features/governance/virtual-keys.mdx
@@ -169,7 +169,8 @@ curl -X DELETE http://localhost:8080/api/governance/virtual-keys/{vk_id}
{
"provider": "openai",
"weight": 0.5,
- "allowed_models": ["gpt-4o-mini"]
+ "allowed_models": ["gpt-4o-mini"],
+ "key_ids": ["openai-primary"]
},
{
"provider": "anthropic",
@@ -178,11 +179,7 @@ curl -X DELETE http://localhost:8080/api/governance/virtual-keys/{vk_id}
}
],
"team_id": "team-eng-001",
- "budget_id": "budget-eng-vk",
- "rate_limit_id": "rate-limit-eng-vk",
- "keys": [
- {"key_id": "8c52039e-38c6-48b2-8016-0bd884b7befb"}
- ]
+ "rate_limit_id": "rate-limit-eng-vk"
},
{
"id": "vk-002",
@@ -202,16 +199,13 @@ curl -X DELETE http://localhost:8080/api/governance/virtual-keys/{vk_id}
"allowed_models": ["claude-3-opus-20240229"]
}
],
- "customer_id": "customer-acme-corp",
- "budget_id": "budget-exec-vk",
- "keys": [
- {"key_id": "8c52039e-38c6-48b2-8016-0bd884b7befb"}
- ]
+ "customer_id": "customer-acme-corp"
}
],
"budgets": [
{
"id": "budget-eng-vk",
+ "virtual_key_id": "vk-001",
"max_limit": 100.00,
"reset_duration": "1M",
"current_usage": 0.0,
@@ -219,6 +213,7 @@ curl -X DELETE http://localhost:8080/api/governance/virtual-keys/{vk_id}
},
{
"id": "budget-exec-vk",
+ "virtual_key_id": "vk-002",
"max_limit": 500.00,
"reset_duration": "1M",
"current_usage": 0.0,
diff --git a/docs/features/litellm-compat.mdx b/docs/features/litellm-compat.mdx
index a4e262b080..490a37efa4 100644
--- a/docs/features/litellm-compat.mdx
+++ b/docs/features/litellm-compat.mdx
@@ -11,7 +11,6 @@ The LiteLLM compatibility plugin provides two transformations:
1. **Text-to-Chat Conversion** - Automatically converts text completion requests to chat completion format for models that only support chat APIs
2. **Chat-to-Responses Conversion** - Automatically converts chat completion requests to responses format for models that only support responses APIs
3. **Drop Unsupported Params** - Automatically drops unsupported parameters if the model doesn't support them
-4. **Unsupported Parameter Conversion** - Automatically converts unsupported parameters to their supported equivalents
When either transformation is applied, responses include `extra_fields.converted_request_type: `. If request parameters are dropped, the keys are added in `extra_fields.dropped_compat_plugin_params`.
@@ -95,16 +94,13 @@ F --> G
1. Open the Bifrost dashboard
-2. Navigate to **Settings** → **Compatibility**
-3. There you can enable the features you need:
+2. Navigate to **Settings** → **Client Configuration**
+3. Expand **LiteLLM Compat** and enable the features you need:
- **Convert Text to Chat** — converts text completion requests to chat for models that only support chat
- **Convert Chat to Responses** — converts chat completion requests to responses for models that only support responses
- **Drop Unsupported Params** — drops unsupported parameters based on model catalog allowlist
- - **Convert Unsupported Params Values** — converts unsupported parameters values to their supported equivalents. See the list of [supported parameters](#supported-parameters) below.
4. Save your configuration
-
-
@@ -115,8 +111,7 @@ F --> G
"compat": {
"convert_text_to_chat": true,
"convert_chat_to_responses": true,
- "should_drop_params": true,
- "should_convert_params": true
+ "should_drop_params": true
}
}
}
@@ -126,14 +121,6 @@ F --> G
-## Supported Parameters
-
-The following parameters are converted to their supported equivalents:
-
-| Parameter | Original Value | Transformed Value | Provider |
-|-----------|----------------|-------------------|----------|
-| messages.role | developer | system | Gemini, Vertex, Bedrock |
-
## Supported Providers
Text completion to chat completion conversion works with any provider that supports chat completions but lacks native text completion support:
@@ -201,12 +188,6 @@ When errors occur on transformed requests:
- `extra_fields.original_model_requested`: The originally requested model
- `extra_fields.dropped_compat_plugin_params`: If any unsupported parameters were dropped, the keys are added here
-## Header Overrides
-
-To enable compat plugins per request, you can use `x-bf-compat` header:
-- When `x-bf-compat: true` or `x-bf-compat: ["*"]`, enables all compat plugins options
-- When `x-bf-compat: ["",""]`, enables the specified settings (available settings: `convert_text_to_chat`, `convert_chat_to_responses`, `should_drop_params`, `should_convert_params`)
-
## What's Preserved
- Model selection and fallback chain
diff --git a/docs/features/observability/prometheus.mdx b/docs/features/observability/prometheus.mdx
index ed2a491759..6c6df6a24f 100644
--- a/docs/features/observability/prometheus.mdx
+++ b/docs/features/observability/prometheus.mdx
@@ -105,12 +105,15 @@ For multi-node cluster deployments, the Prometheus plugin pushes metrics to a [P
{
"plugins": [
{
- "name": "prometheus",
+ "name": "telemetry",
"enabled": true,
"config": {
- "push_gateway_url": "http://pushgateway:9091",
- "job_name": "bifrost",
- "push_interval": 15
+ "push_gateway": {
+ "enabled": true,
+ "push_gateway_url": "http://pushgateway:9091",
+ "job_name": "bifrost",
+ "push_interval": 15
+ }
}
}
]
@@ -123,16 +126,19 @@ For multi-node cluster deployments, the Prometheus plugin pushes metrics to a [P
{
"plugins": [
{
- "name": "prometheus",
+ "name": "telemetry",
"enabled": true,
"config": {
- "push_gateway_url": "http://pushgateway:9091",
- "job_name": "bifrost",
- "push_interval": 15,
- "instance_id": "bifrost-node-1",
- "basic_auth": {
- "username": "admin",
- "password": "secret"
+ "push_gateway": {
+ "enabled": true,
+ "push_gateway_url": "http://pushgateway:9091",
+ "job_name": "bifrost",
+ "push_interval": 15,
+ "instance_id": "bifrost-node-1",
+ "basic_auth": {
+ "username": "admin",
+ "password": "secret"
+ }
}
}
}
diff --git a/docs/integrations/guardrails/azure-content-safety.mdx b/docs/integrations/guardrails/azure-content-safety.mdx
index 0592145f63..d636ecd6f3 100644
--- a/docs/integrations/guardrails/azure-content-safety.mdx
+++ b/docs/integrations/guardrails/azure-content-safety.mdx
@@ -19,25 +19,36 @@ Bifrost integrates with **Azure AI Content Safety** to provide multi-modal conte
## Configuration Fields
-| Field | Type | Required | Default | Description |
-|-------|------|----------|---------|-------------|
-| `endpoint` | string | Yes | - | Azure Content Safety endpoint URL |
-| `api_key` | string | Yes | - | Azure subscription key |
-| `analyze_enabled` | boolean | No | true | Enable content analysis for Hate, Sexual, Violence, SelfHarm |
-| `analyze_severity_threshold` | enum | No | "medium" | Severity level to trigger: `low`, `medium`, or `high` |
-| `jailbreak_shield_enabled` | boolean | No | false | Enable jailbreak detection (input only) |
-| `indirect_attack_shield_enabled` | boolean | No | false | Enable indirect prompt attack detection (input only) |
-| `copyright_enabled` | boolean | No | false | Enable copyrighted content detection (output only) |
-| `text_blocklist_enabled` | boolean | No | false | Enable custom blocklist filtering |
-| `blocklist_names` | array | No | - | List of Azure blocklist names to apply |
+| Field | Type | Required | Default | Description |
+| -------------------------------- | ------- | -------- | -------- | ------------------------------------------------------------ |
+| `endpoint` | string | Yes | - | Azure Content Safety endpoint URL |
+| `api_key` | string | Yes | - | Azure subscription key |
+| `analyze_enabled` | boolean | No | true | Enable content analysis for Hate, Sexual, Violence, SelfHarm |
+| `analyze_severity_threshold` | enum | No | "medium" | Severity level to trigger: `low`, `medium`, or `high` |
+| `jailbreak_shield_enabled` | boolean | No | false | Enable jailbreak detection (input only) |
+| `indirect_attack_shield_enabled` | boolean | No | false | Enable indirect prompt attack detection (input only) |
+| `copyright_enabled` | boolean | No | false | Enable copyrighted content detection (output only) |
+| `text_blocklist_enabled` | boolean | No | false | Enable custom blocklist filtering |
+| `blocklist_names` | array | No | - | List of Azure blocklist names to apply |
+
+## Collecting your API key and URL
+
+Navigate to Azure foundry dashboard
+
+
+
+
+
+- Copy API key to use it in the Azure content moderation config form
+- Copy project endpoint and use base URL as endpoint in the form. e.g. (`https://xxx-resource.services.ai.azure.com`)
## Severity Threshold Levels
-| Threshold | Numeric Value | Behavior |
-|-----------|---------------|----------|
-| `low` | 2 | Most strict - blocks severity 2 and above |
-| `medium` | 4 | Balanced - blocks severity 4 and above |
-| `high` | 6 | Least strict - blocks only severity 6 |
+| Threshold | Numeric Value | Behavior |
+| --------- | ------------- | ----------------------------------------- |
+| `low` | 2 | Most strict - blocks severity 2 and above |
+| `medium` | 4 | Balanced - blocks severity 4 and above |
+| `high` | 6 | Least strict - blocks only severity 6 |
## Detection Categories
@@ -47,23 +58,23 @@ Bifrost integrates with **Azure AI Content Safety** to provide multi-modal conte
- Self-harm
-**Input-only features:** Jailbreak Shield and Indirect Attack Shield only apply to input validation.
-**Output-only features:** Copyright detection only applies to output validation.
+ **Input-only features:** Jailbreak Shield and Indirect Attack Shield only apply to input validation. **Output-only
+ features:** Copyright detection only applies to output validation.
## Provider Capabilities Comparison
-| Capability | AWS Bedrock | Azure Content Safety | GraySwan | Patronus AI |
-|------------|-------------|---------------------|----------|-------------|
-| PII Detection | Yes | No | No | Yes |
-| Content Filtering | Yes | Yes | Yes | Yes |
-| Prompt Injection | Yes | Yes | Yes | Yes |
-| Hallucination Detection | No | No | No | Yes |
-| Toxicity Screening | Yes | Yes | Yes | Yes |
-| Custom Policies | Yes | Yes | Yes | Yes |
-| Custom Natural Language Rules | No | No | Yes | No |
-| Image Support | Yes | No | No | No |
-| IPI Detection | No | Yes | Yes | No |
-| Mutation Detection | No | No | Yes | No |
+| Capability | AWS Bedrock | Azure Content Safety | GraySwan | Patronus AI |
+| ----------------------------- | ----------- | -------------------- | -------- | ----------- |
+| PII Detection | Yes | No | No | Yes |
+| Content Filtering | Yes | Yes | Yes | Yes |
+| Prompt Injection | Yes | Yes | Yes | Yes |
+| Hallucination Detection | No | No | No | Yes |
+| Toxicity Screening | Yes | Yes | Yes | Yes |
+| Custom Policies | Yes | Yes | Yes | Yes |
+| Custom Natural Language Rules | No | No | Yes | No |
+| Image Support | Yes | No | No | No |
+| IPI Detection | No | Yes | Yes | No |
+| Mutation Detection | No | No | Yes | No |
For information on configuring guardrail rules and profiles, see [Guardrails](/enterprise/guardrails).
diff --git a/docs/integrations/langchain-sdk.mdx b/docs/integrations/langchain-sdk.mdx
index 06c219481a..40b5c24ebd 100644
--- a/docs/integrations/langchain-sdk.mdx
+++ b/docs/integrations/langchain-sdk.mdx
@@ -87,8 +87,8 @@ anthropic_llm = ChatAnthropic(
# Google models via Langchain
google_llm = ChatGoogleGenerativeAI(
- model="gemini-2.5-flash",
- base_url=base_url
+ model="gemini-1.5-flash",
+ google_api_base=base_url
)
# All work the same way
@@ -121,8 +121,8 @@ const anthropicLlm = new ChatAnthropic({
// Google models via Langchain
const googleLlm = new ChatGoogleGenerativeAI({
- model: "gemini-2.5-flash",
- baseUrl: baseURL
+ model: "gemini-1.5-flash",
+ baseURL
});
// All work the same way
@@ -193,7 +193,7 @@ from langchain_core.messages import HumanMessage
llm = ChatGoogleGenerativeAI(
model="gemini-2.5-flash",
- base_url="http://localhost:8080/langchain",
+ google_api_base="http://localhost:8080/langchain",
additional_headers={
"x-bf-vk": "your-virtual-key", # Virtual key for governance
}
@@ -326,7 +326,7 @@ import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
const llm = new ChatGoogleGenerativeAI({
model: "gemini-2.5-flash",
- baseUrl: "http://localhost:8080/langchain",
+ baseURL: "http://localhost:8080/langchain",
additionalHeaders: {
"x-bf-vk": "your-virtual-key", // Virtual key for governance
}
diff --git a/docs/media/custom-base-url.mp4 b/docs/media/custom-base-url.mp4
new file mode 100644
index 0000000000..2b32e8e9a3
Binary files /dev/null and b/docs/media/custom-base-url.mp4 differ
diff --git a/docs/media/guardrails/azure-api-key.png b/docs/media/guardrails/azure-api-key.png
index 3a8d86f398..daad86a7be 100644
Binary files a/docs/media/guardrails/azure-api-key.png and b/docs/media/guardrails/azure-api-key.png differ
diff --git a/docs/media/guardrails/microsoft-guardrails-url.png b/docs/media/guardrails/microsoft-guardrails-url.png
new file mode 100644
index 0000000000..f56a220ddd
Binary files /dev/null and b/docs/media/guardrails/microsoft-guardrails-url.png differ
diff --git a/docs/media/setting-up-dashboard-auth.png b/docs/media/setting-up-dashboard-auth.png
index 3fb3ccb253..512273e4a6 100644
Binary files a/docs/media/setting-up-dashboard-auth.png and b/docs/media/setting-up-dashboard-auth.png differ
diff --git a/docs/media/ui-config.png b/docs/media/ui-config.png
index 8ea6ffe88f..7cb29c9bb0 100644
Binary files a/docs/media/ui-config.png and b/docs/media/ui-config.png differ
diff --git a/docs/media/ui-multi-key-for-models.png b/docs/media/ui-multi-key-for-models.png
index 2a049ca4b6..95759fff81 100644
Binary files a/docs/media/ui-multi-key-for-models.png and b/docs/media/ui-multi-key-for-models.png differ
diff --git a/docs/media/ui-provider-configs.png b/docs/media/ui-provider-configs.png
index 20f46c2378..b3c4d3a71a 100644
Binary files a/docs/media/ui-provider-configs.png and b/docs/media/ui-provider-configs.png differ
diff --git a/docs/media/user-provisioning/.custom-attribute-mapping.png-TkS4 b/docs/media/user-provisioning/.custom-attribute-mapping.png-TkS4
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/docs/media/user-provisioning/.custom-attribute-mapping.png-e0eo b/docs/media/user-provisioning/.custom-attribute-mapping.png-e0eo
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/docs/media/user-provisioning/.scim-import-preview.png-sIys b/docs/media/user-provisioning/.scim-import-preview.png-sIys
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/docs/media/user-provisioning/attribute-to-entity-mapping.png b/docs/media/user-provisioning/attribute-to-entity-mapping.png
new file mode 100644
index 0000000000..cad10c4dca
Binary files /dev/null and b/docs/media/user-provisioning/attribute-to-entity-mapping.png differ
diff --git a/docs/media/user-provisioning/custom-attribute-mapping.png b/docs/media/user-provisioning/custom-attribute-mapping.png
new file mode 100644
index 0000000000..1a51ebaa63
Binary files /dev/null and b/docs/media/user-provisioning/custom-attribute-mapping.png differ
diff --git a/docs/media/user-provisioning/entra-app-manifest.png b/docs/media/user-provisioning/entra-app-manifest.png
new file mode 100644
index 0000000000..fa8b3696af
Binary files /dev/null and b/docs/media/user-provisioning/entra-app-manifest.png differ
diff --git a/docs/media/user-provisioning/entra-form.png b/docs/media/user-provisioning/entra-form.png
new file mode 100644
index 0000000000..72df177f1c
Binary files /dev/null and b/docs/media/user-provisioning/entra-form.png differ
diff --git a/docs/media/user-provisioning/gws-apis-and-services.png b/docs/media/user-provisioning/gws-apis-and-services.png
new file mode 100644
index 0000000000..89c8101f0d
Binary files /dev/null and b/docs/media/user-provisioning/gws-apis-and-services.png differ
diff --git a/docs/media/user-provisioning/gws-form.png b/docs/media/user-provisioning/gws-form.png
new file mode 100644
index 0000000000..8cae2fb0d4
Binary files /dev/null and b/docs/media/user-provisioning/gws-form.png differ
diff --git a/docs/media/user-provisioning/okta-form.png b/docs/media/user-provisioning/okta-form.png
new file mode 100644
index 0000000000..4089a3239f
Binary files /dev/null and b/docs/media/user-provisioning/okta-form.png differ
diff --git a/docs/media/user-provisioning/scim-attribute-mapping.png b/docs/media/user-provisioning/scim-attribute-mapping.png
new file mode 100644
index 0000000000..6d48d5e7ba
Binary files /dev/null and b/docs/media/user-provisioning/scim-attribute-mapping.png differ
diff --git a/docs/media/user-provisioning/scim-flow.png b/docs/media/user-provisioning/scim-flow.png
new file mode 100644
index 0000000000..10f1950674
Binary files /dev/null and b/docs/media/user-provisioning/scim-flow.png differ
diff --git a/docs/media/user-provisioning/scim-import-preview.png b/docs/media/user-provisioning/scim-import-preview.png
new file mode 100644
index 0000000000..928fa67825
Binary files /dev/null and b/docs/media/user-provisioning/scim-import-preview.png differ
diff --git a/docs/media/user-provisioning/scim-overview.png b/docs/media/user-provisioning/scim-overview.png
new file mode 100644
index 0000000000..d9f6fcd1f7
Binary files /dev/null and b/docs/media/user-provisioning/scim-overview.png differ
diff --git a/docs/media/user-provisioning/scim-provider-select.png b/docs/media/user-provisioning/scim-provider-select.png
new file mode 100644
index 0000000000..9d2a0854bf
Binary files /dev/null and b/docs/media/user-provisioning/scim-provider-select.png differ
diff --git a/docs/media/user-provisioning/zitadel-assert-roles.png b/docs/media/user-provisioning/zitadel-assert-roles.png
new file mode 100644
index 0000000000..bfbbf5d176
Binary files /dev/null and b/docs/media/user-provisioning/zitadel-assert-roles.png differ
diff --git a/docs/media/user-provisioning/zitadel-client-id.png b/docs/media/user-provisioning/zitadel-client-id.png
new file mode 100644
index 0000000000..15da97d447
Binary files /dev/null and b/docs/media/user-provisioning/zitadel-client-id.png differ
diff --git a/docs/media/user-provisioning/zitadel-form.png b/docs/media/user-provisioning/zitadel-form.png
new file mode 100644
index 0000000000..6fa7884cdb
Binary files /dev/null and b/docs/media/user-provisioning/zitadel-form.png differ
diff --git a/docs/media/user-provisioning/zitadel-project-roles.png b/docs/media/user-provisioning/zitadel-project-roles.png
new file mode 100644
index 0000000000..bdd1b2f363
Binary files /dev/null and b/docs/media/user-provisioning/zitadel-project-roles.png differ
diff --git a/docs/media/user-provisioning/zitadel-refresh-token.png b/docs/media/user-provisioning/zitadel-refresh-token.png
new file mode 100644
index 0000000000..dba6d447f8
Binary files /dev/null and b/docs/media/user-provisioning/zitadel-refresh-token.png differ
diff --git a/docs/media/user-provisioning/zitadel-service-account-create.png b/docs/media/user-provisioning/zitadel-service-account-create.png
new file mode 100644
index 0000000000..f540ac60ef
Binary files /dev/null and b/docs/media/user-provisioning/zitadel-service-account-create.png differ
diff --git a/docs/media/user-provisioning/zitadel-service-account-key.png b/docs/media/user-provisioning/zitadel-service-account-key.png
new file mode 100644
index 0000000000..679e08d1f0
Binary files /dev/null and b/docs/media/user-provisioning/zitadel-service-account-key.png differ
diff --git a/docs/media/user-provisioning/zitadel-service-account-role.png b/docs/media/user-provisioning/zitadel-service-account-role.png
new file mode 100644
index 0000000000..49ebe761f4
Binary files /dev/null and b/docs/media/user-provisioning/zitadel-service-account-role.png differ
diff --git a/docs/media/user-provisioning/zitadel-token-settings.png b/docs/media/user-provisioning/zitadel-token-settings.png
new file mode 100644
index 0000000000..29705c831c
Binary files /dev/null and b/docs/media/user-provisioning/zitadel-token-settings.png differ
diff --git a/docs/media/user-provisioning/zitadel-user-role-assignment.png b/docs/media/user-provisioning/zitadel-user-role-assignment.png
new file mode 100644
index 0000000000..807acadd9d
Binary files /dev/null and b/docs/media/user-provisioning/zitadel-user-role-assignment.png differ
diff --git a/docs/openapi/openapi.json b/docs/openapi/openapi.json
index 585a2062b9..da486a8e81 100644
--- a/docs/openapi/openapi.json
+++ b/docs/openapi/openapi.json
@@ -17675,13 +17675,10 @@
"additionalModelResponseFields": {
"type": "object"
},
- "invokeModelRawChunks": {
- "type": "array",
- "items": {
- "type": "string",
- "format": "byte"
- },
- "description": "Raw bytes for legacy invoke stream. Multiple chunks are needed when a single Bifrost event maps to multiple Anthropic SSE events (e.g., Completed → message_delta + message_stop)."
+ "invokeModelRawChunk": {
+ "type": "string",
+ "format": "byte",
+ "description": "Raw bytes for legacy invoke stream"
}
}
}
@@ -21136,13 +21133,10 @@
"additionalModelResponseFields": {
"type": "object"
},
- "invokeModelRawChunks": {
- "type": "array",
- "items": {
- "type": "string",
- "format": "byte"
- },
- "description": "Raw bytes for legacy invoke stream. Multiple chunks are needed when a single Bifrost event maps to multiple Anthropic SSE events (e.g., Completed → message_delta + message_stop)."
+ "invokeModelRawChunk": {
+ "type": "string",
+ "format": "byte",
+ "description": "Raw bytes for legacy invoke stream"
}
}
}
@@ -24208,13 +24202,10 @@
"additionalModelResponseFields": {
"type": "object"
},
- "invokeModelRawChunks": {
- "type": "array",
- "items": {
- "type": "string",
- "format": "byte"
- },
- "description": "Raw bytes for legacy invoke stream. Multiple chunks are needed when a single Bifrost event maps to multiple Anthropic SSE events (e.g., Completed → message_delta + message_stop)."
+ "invokeModelRawChunk": {
+ "type": "string",
+ "format": "byte",
+ "description": "Raw bytes for legacy invoke stream"
}
}
}
@@ -27166,13 +27157,10 @@
"additionalModelResponseFields": {
"type": "object"
},
- "invokeModelRawChunks": {
- "type": "array",
- "items": {
- "type": "string",
- "format": "byte"
- },
- "description": "Raw bytes for legacy invoke stream. Multiple chunks are needed when a single Bifrost event maps to multiple Anthropic SSE events (e.g., Completed → message_delta + message_stop)."
+ "invokeModelRawChunk": {
+ "type": "string",
+ "format": "byte",
+ "description": "Raw bytes for legacy invoke stream"
}
}
}
@@ -51559,29 +51547,9 @@
"type": "integer",
"description": "Maximum request body size in MB"
},
- "compat": {
- "type": "object",
- "description": "Compat plugin configuration",
- "properties": {
- "convert_text_to_chat": {
- "type": "boolean",
- "description": "Convert text completion requests to chat"
- },
- "convert_chat_to_responses": {
- "type": "boolean",
- "description": "Convert chat completion requests to responses"
- },
- "should_drop_params": {
- "type": "boolean",
- "description": "Drop unsupported parameters based on model catalog"
- },
- "should_convert_params": {
- "type": "boolean",
- "default": false,
- "description": "Converts model parameter values that are not supported by the model"
- }
- },
- "additionalProperties": false
+ "enable_litellm_fallbacks": {
+ "type": "boolean",
+ "description": "Whether LiteLLM fallbacks are enabled"
},
"log_retention_days": {
"type": "integer",
@@ -51794,29 +51762,9 @@
"type": "integer",
"description": "Maximum request body size in MB"
},
- "compat": {
- "type": "object",
- "description": "Compat plugin configuration",
- "properties": {
- "convert_text_to_chat": {
- "type": "boolean",
- "description": "Convert text completion requests to chat"
- },
- "convert_chat_to_responses": {
- "type": "boolean",
- "description": "Convert chat completion requests to responses"
- },
- "should_drop_params": {
- "type": "boolean",
- "description": "Drop unsupported parameters based on model catalog"
- },
- "should_convert_params": {
- "type": "boolean",
- "default": false,
- "description": "Converts model parameter values that are not supported by the model"
- }
- },
- "additionalProperties": false
+ "enable_litellm_fallbacks": {
+ "type": "boolean",
+ "description": "Whether LiteLLM fallbacks are enabled"
},
"log_retention_days": {
"type": "integer",
diff --git a/docs/openapi/openapi.yaml b/docs/openapi/openapi.yaml
index 7142399a8b..a4a6722dbf 100644
--- a/docs/openapi/openapi.yaml
+++ b/docs/openapi/openapi.yaml
@@ -668,6 +668,8 @@ paths:
# Governance - Virtual Keys
/api/governance/virtual-keys:
$ref: './paths/management/governance.yaml#/virtual-keys'
+ /api/governance/virtual-keys/quota:
+ $ref: './paths/management/governance.yaml#/virtual-keys-quota'
/api/governance/virtual-keys/{vk_id}:
$ref: './paths/management/governance.yaml#/virtual-keys-by-id'
diff --git a/docs/openapi/paths/management/governance.yaml b/docs/openapi/paths/management/governance.yaml
index 35e38e1b99..a49d28656e 100644
--- a/docs/openapi/paths/management/governance.yaml
+++ b/docs/openapi/paths/management/governance.yaml
@@ -48,6 +48,39 @@ virtual-keys:
'500':
$ref: '../../openapi.yaml#/components/responses/InternalError'
+virtual-keys-quota:
+ get:
+ operationId: getVirtualKeyQuota
+ summary: Get virtual key quota
+ description: |
+ Returns the budget and rate limit quota for the authenticated virtual key.
+ This is a self-service endpoint — no admin authentication required.
+ The virtual key value itself (provided via header) is the credential.
+ tags:
+ - Governance
+ security:
+ - VirtualKeyAuth: []
+ - BearerAuth: []
+ - ApiKeyAuth: []
+ responses:
+ '200':
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ $ref: '../../schemas/management/governance.yaml#/VirtualKeyQuotaResponse'
+ '401':
+ description: Missing or invalid virtual key
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ '500':
+ $ref: '../../openapi.yaml#/components/responses/InternalError'
+
virtual-keys-by-id:
get:
operationId: getVirtualKey
diff --git a/docs/openapi/schemas/integrations/bedrock/converse.yaml b/docs/openapi/schemas/integrations/bedrock/converse.yaml
index ef878b5b56..b6b73a652e 100644
--- a/docs/openapi/schemas/integrations/bedrock/converse.yaml
+++ b/docs/openapi/schemas/integrations/bedrock/converse.yaml
@@ -332,12 +332,10 @@ BedrockStreamEvent:
type: object
additionalModelResponseFields:
type: object
- invokeModelRawChunks:
- type: array
- items:
- type: string
- format: byte
- description: Raw bytes for legacy invoke stream. Multiple chunks are needed when a single Bifrost event maps to multiple Anthropic SSE events (e.g., Completed → message_delta + message_stop).
+ invokeModelRawChunk:
+ type: string
+ format: byte
+ description: Raw bytes for legacy invoke stream
BedrockContentBlockDelta:
type: object
diff --git a/docs/openapi/schemas/management/governance.yaml b/docs/openapi/schemas/management/governance.yaml
index c195faeb94..bd04aecee7 100644
--- a/docs/openapi/schemas/management/governance.yaml
+++ b/docs/openapi/schemas/management/governance.yaml
@@ -331,6 +331,24 @@ ListVirtualKeysResponse:
count:
type: integer
+VirtualKeyQuotaResponse:
+ type: object
+ description: Virtual key quota response (self-service, no admin auth required)
+ properties:
+ virtual_key_name:
+ type: string
+ description: Name of the virtual key
+ is_active:
+ type: boolean
+ description: Whether the virtual key is active
+ budgets:
+ type: array
+ description: Budget quotas assigned to this virtual key
+ items:
+ $ref: '#/Budget'
+ rate_limit:
+ $ref: '#/RateLimit'
+
VirtualKeyResponse:
type: object
description: Virtual key operation response
diff --git a/docs/overview.mdx b/docs/overview.mdx
index 2960604cf3..e1d7cd33ad 100644
--- a/docs/overview.mdx
+++ b/docs/overview.mdx
@@ -122,9 +122,6 @@ Advanced capabilities for teams running production AI systems at scale. Enterpri
Transform existing enterprise APIs into MCP tools using federated authentication — no code required.
-
- Secure key management with HashiCorp Vault, AWS Secrets Manager, Google Secret Manager, and Azure Key Vault.
-
Deploy within your private cloud infrastructure with VPC isolation and enhanced security controls.
diff --git a/docs/plugins/building-dynamic-binary.mdx b/docs/plugins/building-dynamic-binary.mdx
index 1c46d8e749..9d1329e118 100644
--- a/docs/plugins/building-dynamic-binary.mdx
+++ b/docs/plugins/building-dynamic-binary.mdx
@@ -98,7 +98,7 @@ Use this for Alpine-based deployments or when you want minimal image size.
```dockerfile
-# --- UI Build Stage: Build the Next.js frontend ---
+# --- UI Build Stage: Build the React + Vite frontend ---
FROM node:25-alpine3.23 AS ui-builder
WORKDIR /app
@@ -110,8 +110,7 @@ RUN npm ci
COPY ui/ ./
# Build UI (skip the copy-build step)
-RUN npx next build
-RUN node scripts/fix-paths.js
+RUN npm run build-enterprise
# --- Go Build Stage: Compile the Go binary ---
FROM golang:1.26.1-alpine3.23 AS builder
@@ -215,7 +214,7 @@ Use this for Debian/Ubuntu-based deployments or when deploying to glibc-based sy
```dockerfile
-# --- UI Build Stage: Build the Next.js frontend ---
+# --- UI Build Stage: Build the React + Vite frontend ---
FROM node:25-bookworm AS ui-builder
WORKDIR /app
@@ -227,8 +226,7 @@ RUN npm ci
COPY ui/ ./
# Build UI
-RUN npx next build
-RUN node scripts/fix-paths.js
+RUN npm run build-enterprise
# --- Go Build Stage: Compile the Go binary ---
FROM golang:1.26.1-bookworm AS builder
diff --git a/docs/plugins/writing-go-plugin.mdx b/docs/plugins/writing-go-plugin.mdx
index 146bdd8e28..0f159478f8 100644
--- a/docs/plugins/writing-go-plugin.mdx
+++ b/docs/plugins/writing-go-plugin.mdx
@@ -1138,7 +1138,9 @@ plugin was built with a different version of package github.com/maximhq/bifrost/
```json
{
- "log_level": "debug",
+ "client": {
+ "enable_logging": true
+ },
"plugins": [
{
"enabled": true,
@@ -1150,6 +1152,8 @@ plugin was built with a different version of package github.com/maximhq/bifrost/
}
```
+For verbose plugin-loader logs, set `BIFROST_LOG_LEVEL=debug` in the environment.
+
**Check plugin symbols:**
```bash
diff --git a/docs/providers/request-options.mdx b/docs/providers/request-options.mdx
index b542e702fa..55d91481a7 100644
--- a/docs/providers/request-options.mdx
+++ b/docs/providers/request-options.mdx
@@ -16,7 +16,9 @@ Bifrost provides request options that control behavior, enable features, and pas
| `BifrostContextKeySessionID` | `x-bf-session-id` | `string` | Session ID for key stickiness (requires KV store) |
| `BifrostContextKeySessionTTL` | `x-bf-session-ttl` | `time.Duration` | Session-to-key cache TTL (duration string or seconds) |
| `BifrostContextKeyRequestID` | `x-request-id` | `string` | Custom request ID for tracking |
-| `BifrostContextKeySendBackRawResponse` | `x-bf-send-back-raw-response` | `bool` | Include raw provider response |
+| `BifrostContextKeySendBackRawRequest` | `x-bf-send-back-raw-request` | `bool` | Include raw provider request in the response |
+| `BifrostContextKeySendBackRawResponse` | `x-bf-send-back-raw-response` | `bool` | Include raw provider response in the response |
+| `BifrostContextKeyStoreRawRequestResponse` | `x-bf-store-raw-request-response` | `bool` | Persist raw request/response in log records |
| `BifrostContextKeyPassthroughExtraParams` | `x-bf-passthrough-extra-params` | `bool` | Enable passthrough for extra parameters |
| `BifrostContextKeyExtraHeaders` | `x-bf-eh-*` | `map[string][]string` | Custom headers forwarded to provider |
| `BifrostContextKeyDirectKey` | `-` | `schemas.Key` | Direct key credentials (Go SDK only) |
@@ -269,14 +271,69 @@ response, err := client.ChatCompletionRequest(schemas.NewBifrostContext(ctx, sch
+### Send Back Raw Request
+
+**Context Key:** `BifrostContextKeySendBackRawRequest`
+**Header:** `x-bf-send-back-raw-request`
+**Type:** `bool` (header values: `"true"` or `"false"`)
+**Required:** No
+
+Include the exact JSON body sent to the provider alongside Bifrost's standardized response. Accepts `"true"` or `"false"` — either value fully overrides the provider-level `send_back_raw_request` config for this request.
+
+
+
+```bash
+curl --location 'http://localhost:8080/v1/chat/completions' \
+--header 'x-bf-send-back-raw-request: true' \
+--header 'Content-Type: application/json' \
+--data '{
+ "model": "openai/gpt-4o-mini",
+ "messages": [{"role": "user", "content": "Hello!"}]
+}'
+```
+
+
+```go
+ctx := context.Background()
+ctx = context.WithValue(ctx, schemas.BifrostContextKeySendBackRawRequest, true)
+
+response, err := client.ChatCompletionRequest(schemas.NewBifrostContext(ctx, schemas.NoDeadline), &schemas.BifrostChatRequest{
+ Provider: schemas.OpenAI,
+ Model: "gpt-4o-mini",
+ Input: messages,
+})
+
+// Access raw request
+if response.ChatResponse != nil {
+ rawReq := response.ChatResponse.ExtraFields.RawRequest
+}
+```
+
+
+
+The raw request appears in `extra_fields.raw_request`:
+
+```json
+{
+ "choices": [...],
+ "usage": {...},
+ "extra_fields": {
+ "provider": "openai",
+ "raw_request": {
+ // Exact JSON sent to the provider
+ }
+ }
+}
+```
+
### Send Back Raw Response
**Context Key:** `BifrostContextKeySendBackRawResponse`
**Header:** `x-bf-send-back-raw-response`
-**Type:** `bool` (header value: `"true"`)
+**Type:** `bool` (header values: `"true"` or `"false"`)
**Required:** No
-Include the original provider response alongside Bifrost's standardized response format.
+Include the original provider response alongside Bifrost's standardized response format. Accepts `"true"` or `"false"` — either value fully overrides the provider-level `send_back_raw_response` config for this request.
@@ -324,6 +381,51 @@ The raw response appears in `extra_fields.raw_response`:
}
```
+### Store Raw Request/Response
+
+**Context Key:** `BifrostContextKeyStoreRawRequestResponse`
+**Header:** `x-bf-store-raw-request-response`
+**Type:** `bool` (header values: `"true"` or `"false"`)
+**Required:** No
+
+Persist the raw provider request and response in the log record. Accepts `"true"` or `"false"` — either value fully overrides the provider-level `store_raw_request_response` config for this request.
+
+This is orthogonal to the send-back flags: enabling this does not affect whether raw data appears in the API response, and enabling send-back does not automatically store raw data in logs. Use this when you want observability into provider payloads without necessarily exposing them to the caller, or combine it with `x-bf-send-back-raw-*` to do both.
+
+
+
+```bash
+curl --location 'http://localhost:8080/v1/chat/completions' \
+--header 'x-bf-store-raw-request-response: true' \
+--header 'Content-Type: application/json' \
+--data '{
+ "model": "openai/gpt-4o-mini",
+ "messages": [{"role": "user", "content": "Hello!"}]
+}'
+```
+
+
+```go
+ctx := context.Background()
+ctx = context.WithValue(ctx, schemas.BifrostContextKeyStoreRawRequestResponse, true)
+
+response, err := client.ChatCompletionRequest(schemas.NewBifrostContext(ctx, schemas.NoDeadline), &schemas.BifrostChatRequest{
+ Provider: schemas.OpenAI,
+ Model: "gpt-4o-mini",
+ Input: messages,
+})
+// Raw data is persisted in the log record.
+// ExtraFields.RawRequest/RawResponse are nil unless send-back flags are also enabled.
+```
+
+
+
+
+`x-bf-store-raw-request-response` only has effect when the logging plugin is active — raw data is written to the log record by the logging plugin. Without it, enabling this flag captures the data but nothing persists it.
+
+`x-bf-store-raw-request-response` and `x-bf-send-back-raw-*` are orthogonal — you can enable any combination. Enabling store does not send data back to the caller; enabling send-back does not persist data in logs. Enable both to do both.
+
+
### Passthrough Extra Parameters
**Context Key:** `BifrostContextKeyPassthroughExtraParams`
diff --git a/docs/quickstart/gateway/provider-configuration.mdx b/docs/quickstart/gateway/provider-configuration.mdx
index 5986ed87f6..597d3b2a1e 100644
--- a/docs/quickstart/gateway/provider-configuration.mdx
+++ b/docs/quickstart/gateway/provider-configuration.mdx
@@ -337,7 +337,12 @@ Override the default API endpoint for a provider. This is useful for connecting
-
+
+
+
+
1. Navigate to **"Model Providers"** → **"Configurations"** → **"OpenAI"** → **"Provider level configuration"** → **"Network config"**
2. Set **Base URL**: `http://localhost:8000/v1`
@@ -860,6 +865,10 @@ curl --location 'http://localhost:8080/api/providers' \
Include the original provider response alongside Bifrost's standardized response format. Useful for debugging and accessing provider-specific metadata.
+
+You can override this per request using the `x-bf-send-back-raw-response` header (`"true"` or `"false"`), regardless of the provider-level config. See [Request Options](../../providers/request-options#send-back-raw-response) for details.
+
+
@@ -936,6 +945,10 @@ When enabled, the raw provider response appears in `extra_fields.raw_response`:
Include the original request sent to the provider alongside Bifrost's response. Useful for debugging request transformations and verifying what was actually sent to the provider.
+
+You can override this per request using the `x-bf-send-back-raw-request` header (`"true"` or `"false"`), regardless of the provider-level config. See [Request Options](../../providers/request-options#send-back-raw-request) for details.
+
+
@@ -1012,6 +1025,71 @@ When enabled, the raw provider request appears in `extra_fields.raw_request`:
You can enable both `send_back_raw_request` and `send_back_raw_response` together to see the complete request-response cycle for debugging purposes.
+### Store Raw Request/Response
+
+Persist the raw provider request and response in the log record. This is orthogonal to `send_back_raw_request` and `send_back_raw_response` — enabling this does not affect whether raw data appears in the API response, and enabling send-back does not automatically store raw data in logs. Enable both to do both.
+
+
+
+
+
+1. Navigate to **"Model Providers"** → **"Configurations"** → **{Provider}** → **"Provider level configuration"** → **"Performance tuning"**
+2. Toggle **"Store Raw Request/Response"** to enabled
+3. Save configuration
+
+
+
+
+
+```bash
+curl --location 'http://localhost:8080/api/providers' \
+--header 'Content-Type: application/json' \
+--data '{
+ "provider": "openai",
+ "keys": [
+ {
+ "name": "openai-key-1",
+ "value": "env.OPENAI_API_KEY",
+ "models": ["*"],
+ "weight": 1.0
+ }
+ ],
+ "store_raw_request_response": true
+}'
+```
+
+
+
+
+
+```json
+{
+ "providers": {
+ "openai": {
+ "keys": [
+ {
+ "name": "openai-key-1",
+ "value": "env.OPENAI_API_KEY",
+ "models": ["*"],
+ "weight": 1.0
+ }
+ ],
+ "store_raw_request_response": true
+ }
+ }
+}
+```
+
+
+
+
+
+
+`store_raw_request_response` only has effect when the logging plugin is active — raw data is written to the log record by the logging plugin. Without it, enabling this flag captures the data but nothing persists it.
+
+You can override this per request using the `x-bf-store-raw-request-response` header (`"true"` or `"false"`), regardless of the provider-level config. See [Request Options](../../providers/request-options#store-raw-requestresponse) for details.
+
+
### Passthrough Extra Parameters
Enable passthrough mode for extra parameters. When enabled, any parameters in the `extra_params` field (or provider-specific extra parameter fields) will be merged directly into the request sent to the provider, bypassing Bifrost's parameter filtering.
diff --git a/docs/quickstart/gateway/setting-up-auth.mdx b/docs/quickstart/gateway/setting-up-auth.mdx
index 3e01340ff1..bf29677feb 100644
--- a/docs/quickstart/gateway/setting-up-auth.mdx
+++ b/docs/quickstart/gateway/setting-up-auth.mdx
@@ -4,6 +4,8 @@ description: "Learn how to enable basic authentication for the Bifrost dashboard
icon: "lock"
---
+This feature is only available in OSS. For enterprise builds you can setup [SCIM](/enterprise/scim)
+
## Overview
Bifrost provides built-in authentication to protect your dashboard and admin API endpoints. When enabled, users must log in with credentials before accessing the dashboard or making admin API calls. This feature helps secure your Bifrost instance, especially when deployed in production environments.
@@ -25,7 +27,8 @@ Bifrost provides built-in authentication to protect your dashboard and admin API
3. Enter your **Password** in the admin password field
-The username and password fields are only enabled when the authentication toggle is turned on. Make sure to use a strong password for security.
+ The username and password fields are only enabled when the authentication toggle is turned on. Make sure to use a
+ strong password for security.
### Step 3: Configure Inference Call Authentication (Optional)
@@ -39,7 +42,8 @@ By default, when authentication is enabled, all API calls (including inference c
- MCP tool execution calls will still require authentication
-This option is useful if you want to protect your dashboard and admin functions while allowing public access to inference endpoints.
+ This option is useful if you want to protect your dashboard and admin functions while allowing public access to
+ inference endpoints.
### Step 4: Configure Whitelisted Routes (Optional)
@@ -54,12 +58,15 @@ You can configure specific routes that bypass the authentication middleware enti
**Wildcard support:** Routes ending with `*` are treated as prefix matches. For example, `/api/webhook*` will match `/api/webhook`, `/api/webhook/v1`, `/api/webhook/github`, etc.
**Example values:**
+
```
/api/custom-webhook, /api/public-endpoint, /api/webhook*
```
-System routes like `/health`, `/api/session/login`, `/api/session/is-auth-enabled`, `/api/oauth/callback`, and `/api/info` are always whitelisted regardless of this setting. Whitelisted routes only apply to dashboard and admin API endpoints — inference endpoints have their own toggle (see Step 3).
+ System routes like `/health`, `/api/session/login`, `/api/session/is-auth-enabled`, `/api/oauth/callback`, and
+ `/api/info` are always whitelisted regardless of this setting. Whitelisted routes only apply to dashboard and admin
+ API endpoints — inference endpoints have their own toggle (see Step 3).
### Step 5: Save Changes
@@ -99,6 +106,7 @@ When authentication is enabled for inference calls (i.e., the "Disable authentic
- **Basic Authentication**: Username and Password in Basic auth
- **Bearer Token**: base64 string of username:password as bearer token
+
### Whitelisted Routes
When a route is added to the whitelisted routes list in Security settings, requests to that path bypass authentication entirely — no Basic Auth or Bearer Token is required. This applies only to dashboard and admin API endpoints. Inference endpoints are controlled separately via the "Disable authentication on inference calls" toggle.
@@ -132,4 +140,4 @@ To disable authentication:
2. Toggle off the **Password protect the dashboard** switch
3. Click **Save Changes**
-After disabling, the dashboard will be accessible without authentication immediately.
\ No newline at end of file
+After disabling, the dashboard will be accessible without authentication immediately.
diff --git a/docs/quickstart/gateway/setting-up.mdx b/docs/quickstart/gateway/setting-up.mdx
index d5195e2023..7dd58228a9 100644
--- a/docs/quickstart/gateway/setting-up.mdx
+++ b/docs/quickstart/gateway/setting-up.mdx
@@ -48,15 +48,15 @@ docker pull maximhq/bifrost:v1.3.9-arm64
# For configuration persistence across restarts
docker run -p 8080:8080 -v $(pwd)/data:/app/data maximhq/bifrost
```
-### 2. Configuration Flags
-| Flag | Default | NPX | Docker | Description |
-|------|---------|-----|--------|-------------|
-| port | 8080 | `-port 8080` | `-e APP_PORT=8080 -p 8080:8080` | HTTP server port |
-| host | localhost | `-host 0.0.0.0` | `-e APP_HOST=0.0.0.0` | Host to bind server to |
-| log-level | info | `-log-level info` | `-e LOG_LEVEL=info` | Log level (debug, info, warn, error) |
-| log-style | json | `-log-style json` | `-e LOG_STYLE=json` | Log style (pretty, json) |
+### 2. Configuration Flags
+| Flag | Default | NPX | Docker | Description |
+| --------- | --------- | ----------------- | ------------------------------- | ------------------------------------ |
+| port | 8080 | `-port 8080` | `-e APP_PORT=8080 -p 8080:8080` | HTTP server port |
+| host | localhost | `-host 0.0.0.0` | `-e APP_HOST=0.0.0.0` | Host to bind server to |
+| log-level | info | `-log-level info` | `-e LOG_LEVEL=info` | Log level (debug, info, warn, error) |
+| log-style | json | `-log-style json` | `-e LOG_STYLE=json` | Log style (pretty, json) |
**Understanding App Directory**
@@ -72,8 +72,9 @@ npx -y @maximhq/bifrost -app-dir ./my-bifrost-data
```
**What's stored in app-dir:**
+
- `config.json` - Configuration file (optional)
-- `config.db` - SQLite database for UI configuration
+- `config.db` - SQLite database for UI configuration
- `logs.db` - Request logs database
**Note:** When using Bifrost via Docker, the volume you mount will be used as the app-dir.
@@ -86,7 +87,7 @@ Navigate to **http://localhost:8080** in your browser:
# macOS
open http://localhost:8080
-# Linux
+# Linux
xdg-open http://localhost:8080
# Windows
@@ -94,6 +95,7 @@ start http://localhost:8080
```
🖥️ **The Web UI provides:**
+
- **Visual provider setup** - Add API keys with clicks, not code
- **Real-time configuration** - Changes apply immediately
- **Live monitoring** - Request logs, metrics, and analytics
@@ -131,17 +133,21 @@ Bifrost supports **two configuration approaches** - you cannot use both simultan

**When the UI is available:**
+
- No `config.json` file exists (Bifrost auto-creates SQLite database)
- `config.json` exists with `config_store` configured
### Mode 2: File-based Configuration
+You can view entire config schema [here](https://www.getbifrost.ai/schema)
+
**When to use:** Advanced setups, GitOps workflows, or when UI is not needed
Create `config.json` in your app directory:
```json
{
+ "$schema": "https://www.getbifrost.ai/schema",
"client": {
"drop_excess_requests": false
},
@@ -168,12 +174,14 @@ Create `config.json` in your app directory:
```
**Without `config_store` in `config.json`:**
+
- **UI is disabled** - no real-time configuration possible
- **Read-only mode** - `config.json` is never modified
- **Memory-only** - all configurations loaded into memory at startup
- **Restart required** - changes to `config.json` only apply after restart
**With `config_store` in `config.json`:**
+
- **UI is enabled** - full real-time configuration via web interface
- **Database check** - Bifrost checks if config store database exists and has data
- **Empty DB**: Bootstraps database with `config.json` settings, then uses DB exclusively
@@ -184,12 +192,22 @@ Create `config.json` in your app directory:
If you want database persistence but prefer not to use the UI, note that modifying `config.json` after initial bootstrap has no effect when `config_store` is enabled. Use the public HTTP APIs to make configuration changes instead.
**The Three Stores Explained:**
+
- **Config Store**: Stores provider configs, API keys, MCP settings - Required for UI functionality
-- **Logs Store**: Stores request logs shown in UI - Optional, can be disabled
+- **Logs Store**: Stores request logs shown in UI - Optional, can be disabled
- **Vector Store**: Used for semantic caching - Optional, can be disabled
## PostgreSQL UTF8 Requirement
+
+ The minimum PostgreSQL version required is 16 or above.
+
+
+
+ For the log store, Bifrost creates materialized views to improve analytics performance. Ensure that the PostgreSQL user
+ has the necessary permissions to perform these operations on the target schema.
+
+
If you use PostgreSQL for `config_store` or `logs_store`, the target database must use `UTF8` encoding.
Use `template0` when creating the database so PostgreSQL applies UTF8 and locale settings explicitly:
diff --git a/docs/quickstart/go-sdk/provider-configuration.mdx b/docs/quickstart/go-sdk/provider-configuration.mdx
index 87c2901ece..234551522f 100644
--- a/docs/quickstart/go-sdk/provider-configuration.mdx
+++ b/docs/quickstart/go-sdk/provider-configuration.mdx
@@ -328,16 +328,35 @@ func (a *MyAccount) GetConfigForProvider(provider schemas.ModelProvider) (*schem
Include the original provider response alongside Bifrost's standardized response format. Useful for debugging and accessing provider-specific metadata.
+**Provider-level default** (applies to all requests for this provider):
+
```go
-func (a *MyAccount) GetConfigForProvider(ctx *context.Context, provider schemas.ModelProvider) (*schemas.ProviderConfig, error) {
+func (a *MyAccount) GetConfigForProvider(provider schemas.ModelProvider) (*schemas.ProviderConfig, error) {
return &schemas.ProviderConfig{
NetworkConfig: schemas.DefaultNetworkConfig,
ConcurrencyAndBufferSize: schemas.DefaultConcurrencyAndBufferSize,
- SendBackRawResponse: true, // Include raw provider response
+ SendBackRawResponse: true,
}, nil
}
```
+**Per-request override** (overrides the provider default for a single request):
+
+```go
+ctx := context.Background()
+ctx = context.WithValue(ctx, schemas.BifrostContextKeySendBackRawResponse, true) // or false to suppress
+
+response, err := client.ChatCompletionRequest(schemas.NewBifrostContext(ctx, schemas.NoDeadline), &schemas.BifrostChatRequest{
+ Provider: schemas.OpenAI,
+ Model: "gpt-4o-mini",
+ Input: messages,
+})
+
+if response.ChatResponse != nil {
+ rawResp := response.ChatResponse.ExtraFields.RawResponse // original provider JSON
+}
+```
+
When enabled, the raw provider response appears in `ExtraFields.RawResponse`:
```go
@@ -368,16 +387,35 @@ type BifrostResponseExtraFields struct {
Include the original request sent to the provider alongside Bifrost's response. Useful for debugging request transformations and verifying what was actually sent to the provider.
+**Provider-level default** (applies to all requests for this provider):
+
```go
-func (a *MyAccount) GetConfigForProvider(ctx *context.Context, provider schemas.ModelProvider) (*schemas.ProviderConfig, error) {
+func (a *MyAccount) GetConfigForProvider(provider schemas.ModelProvider) (*schemas.ProviderConfig, error) {
return &schemas.ProviderConfig{
NetworkConfig: schemas.DefaultNetworkConfig,
ConcurrencyAndBufferSize: schemas.DefaultConcurrencyAndBufferSize,
- SendBackRawRequest: true, // Include raw provider request
+ SendBackRawRequest: true,
}, nil
}
```
+**Per-request override** (overrides the provider default for a single request):
+
+```go
+ctx := context.Background()
+ctx = context.WithValue(ctx, schemas.BifrostContextKeySendBackRawRequest, true) // or false to suppress
+
+response, err := client.ChatCompletionRequest(schemas.NewBifrostContext(ctx, schemas.NoDeadline), &schemas.BifrostChatRequest{
+ Provider: schemas.OpenAI,
+ Model: "gpt-4o-mini",
+ Input: messages,
+})
+
+if response.ChatResponse != nil {
+ rawReq := response.ChatResponse.ExtraFields.RawRequest // exact JSON sent to the provider
+}
+```
+
When enabled, the raw provider request appears in `ExtraFields.RawRequest`:
```go
@@ -388,9 +426,42 @@ type BifrostResponseExtraFields struct {
}
```
-
-You can enable both `SendBackRawRequest` and `SendBackRawResponse` together to see the complete request-response cycle for debugging purposes.
-
+### Store Raw Request/Response
+
+Persist the raw provider request and response in the log record without necessarily returning them in the API response. This is orthogonal to the send-back flags — enabling this does not affect what the caller receives, and enabling send-back does not automatically store data in logs. Enable both to do both.
+
+**Provider-level default** (applies to all requests for this provider):
+
+```go
+func (a *MyAccount) GetConfigForProvider(provider schemas.ModelProvider) (*schemas.ProviderConfig, error) {
+ return &schemas.ProviderConfig{
+ NetworkConfig: schemas.DefaultNetworkConfig,
+ ConcurrencyAndBufferSize: schemas.DefaultConcurrencyAndBufferSize,
+ StoreRawRequestResponse: true,
+ }, nil
+}
+```
+
+**Per-request override** (overrides the provider default for a single request):
+
+```go
+ctx := context.Background()
+ctx = context.WithValue(ctx, schemas.BifrostContextKeyStoreRawRequestResponse, true) // or false to disable
+
+response, err := client.ChatCompletionRequest(schemas.NewBifrostContext(ctx, schemas.NoDeadline), &schemas.BifrostChatRequest{
+ Provider: schemas.OpenAI,
+ Model: "gpt-4o-mini",
+ Input: messages,
+})
+// Raw data is persisted in the log record.
+// ExtraFields.RawRequest/RawResponse are nil unless send-back flags are also enabled.
+```
+
+
+`StoreRawRequestResponse` only has effect when the logging plugin is active — raw data is written to the log record by the logging plugin. Without it, enabling this flag captures the data but nothing persists it.
+
+`StoreRawRequestResponse`, `SendBackRawRequest`, and `SendBackRawResponse` are orthogonal controls — enabling any one does not imply the others. Enable any combination depending on whether you need raw data in logs, in the response, or both.
+
## Provider-Specific Authentication