{"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://www.getbifrost.ai/schema","title":"Bifrost Configuration Schema","description":"Schema for Bifrost HTTP transport configuration","type":"object","properties":{"$schema":{"type":"string","description":"Schema location for IDE validation. Defaults to \"https://www.getbifrost.ai/schema\" and can be set to a mirrored URL, file:// URL, or filesystem path for isolated deployments.","default":"https://www.getbifrost.ai/schema"},"server":{"type":"object","properties":{"read_buffer_size":{"type":"integer","description":"Read buffer size in bytes. This controls the size of the buffer used for reading HTTP headers.","default":65536},"plugin_download_private_allowlist":{"type":"array","items":{"type":"string"},"description":"Hostnames, IP addresses, or CIDR ranges that custom plugin (.so) downloads are allowed to reach even when they resolve to a private/loopback/link-local/CGNAT address (blocked by default to prevent SSRF). Use only for trusted internal artifact hosts. Deploy-time only - read from config.json/environment at startup, never settable via the plugin admin API."}},"required":["read_buffer_size"]},"version":{"type":"integer","description":"Controls how empty arrays in allow-list fields (models, allowed_models, key_ids, tools_to_execute) are interpreted. Omit or set to 2 for v1.5.0+ semantics: empty = deny all, [\"*\"] = allow all. Set to 1 to restore v1.4.x semantics: empty = allow all.","enum":[1,2],"default":2},"source_of_truth":{"type":"string","description":"Controls how config.json is reconciled with the config store. \"split\" preserves existing merge behavior. \"config.json\" makes present config.json sections authoritative during startup sync; missing sections leave database rows untouched.","enum":["split","config.json"],"default":"split"},"encryption_key":{"type":"string","description":"You can set the value as env.<ENV_VAR_NAME> to use an environment variable. We also read encryption key from BIFROST_ENCRYPTION_KEY environment variable. Note: once set, the encryption key cannot be changed unless you clean up the database. Accepts any string; a secure 32-byte AES-256 key will be derived using Argon2id KDF. If not provided, data will be saved in plain text. Recommended: use a passphrase of at least 16 bytes for better security"},"env_label":{"type":"string","description":"Short label (max 10 characters) displayed in the management UI sidebar to identify the environment (e.g. \"staging\", \"prod\"). Overrides the BIFROST_ENV_LABEL environment variable when both are set.","maxLength":10},"auth_config":{"$ref":"#/$defs/auth_config"},"setup_token":{"type":"string","pattern":"\\S","description":"Operator-provisioned bootstrap secret required to create the very first admin account when no admin account is configured yet. You can set the value as env.<ENV_VAR_NAME> to use an environment variable, or as vault.<path> to use a Vault secret reference. We also read the setup token from the BIFROST_SETUP_TOKEN environment variable. Identical across every node in a multi-node deployment (unlike a randomly generated value), so any node can authorize first-admin creation. Never persisted and never logged; if left unset, creating the first admin account is rejected until this is configured. Whitespace-only values are rejected."},"client":{"type":"object","description":"Client configuration settings","properties":{"drop_excess_requests":{"type":"boolean","description":"Whether to drop excess requests when pool is full"},"initial_pool_size":{"type":"integer","minimum":1,"description":"Initial size of the connection pool","default":300},"prometheus_labels":{"type":"array","items":{"type":"string"},"description":"Labels to use for Prometheus metrics"},"allowed_origins":{"type":"array","items":{"anyOf":[{"type":"string","const":"*"},{"type":"string","format":"uri"}]},"description":"CORS allowed origins (supports \"*\" or URI strings)"},"enable_logging":{"type":"boolean","description":"Enable request/response logging"},"disable_content_logging":{"type":"boolean","description":"Disable logging of sensitive content (inputs, outputs, embeddings, etc.)"},"retain_content_in_object_storage":{"type":"boolean","description":"When content logging is disabled, offload the full content to object storage as hidden instead of dropping it. Hidden content is never served back through the API/UI — the database row stays metadata-only and is only readable with direct access to the storage bucket. Requires object storage to be configured on the logs store.","default":false},"allow_per_request_content_storage_override":{"type":"boolean","description":"Allow individual requests to override content storage via the x-bf-disable-content-logging header or context key, and to opt in to raw-byte persistence in logs via x-bf-store-raw-request-response. When false (default), the global disable_content_logging setting is authoritative and per-request storage overrides are ignored. Does not control sending raw bytes back to callers — see allow_per_request_raw_override.","default":false},"allow_per_request_raw_override":{"type":"boolean","description":"Allow individual requests to send raw provider request/response bytes back to the caller via the x-bf-send-back-raw-request and x-bf-send-back-raw-response headers. When false (default), the provider-level send_back_raw_request/response settings are authoritative and per-request overrides are ignored. Does not affect raw-byte persistence in logs — see allow_per_request_content_storage_override.","default":false},"disable_db_pings_in_health":{"type":"boolean","description":"Disable DB pings in health check","default":false},"dump_errors_in_console_logs":{"type":"boolean","description":"Dump full error details to the server console logs. Useful for debugging; may be noisy in production.","default":false},"webhook_config":{"type":"object","description":"Global webhook delivery settings; per-endpoint tuning lives on each endpoint. Read at server startup.","properties":{"delivery_history_retention_days":{"type":"integer","minimum":1,"default":30,"description":"How long delivery history records are kept before being reaped."}},"additionalProperties":false},"log_retention_days":{"type":"integer","minimum":1,"description":"Number of days to retain logs","default":365},"enforce_governance_header":{"type":"boolean","description":"Deprecated: use enforce_auth_on_inference"},"enforce_auth_on_inference":{"type":"boolean","description":"Require auth (VK, API key, or user token) on inference endpoints"},"dual_credential_conflict_behavior":{"type":"string","enum":["error","prefer_vk","prefer_idp"],"description":"Controls behavior when an inference request carries both an IDP access token (Authorization: Bearer <jwt>) and a virtual key (x-bf-vk header). 'error' rejects with 400. 'prefer_vk' drops the IDP token and authenticates via the VK. 'prefer_idp' (default) uses the IDP token for identity."},"enforce_scim_auth":{"type":"boolean","description":"Deprecated: use enforce_auth_on_inference"},"max_request_body_size_mb":{"type":"integer","minimum":1,"description":"Maximum request body size in MB"},"compat":{"type":"object","description":"Compat plugin configuration for request type conversion, parameter dropping, and parameter value conversion","properties":{"convert_text_to_chat":{"type":"boolean","description":"Convert text completion requests to chat for models that only support chat"},"convert_chat_to_responses":{"type":"boolean","description":"Convert chat completion requests to responses for models that only support responses"},"should_drop_params":{"type":"boolean","description":"Drop unsupported parameters based on model catalog allowlist"},"should_convert_params":{"type":"boolean","description":"Converts model parameter values that are not supported by the model.","default":false}},"additionalProperties":false},"header_filter_config":{"type":"object","description":"Global header filtering configuration for x-bf-eh-* headers forwarded to LLM providers","properties":{"allowlist":{"type":"array","items":{"type":"string"},"description":"If non-empty, only these headers (from x-bf-eh-* prefix) are allowed to be forwarded"},"denylist":{"type":"array","items":{"type":"string"},"description":"Headers to always block from being forwarded"}},"additionalProperties":false},"async_job_result_ttl":{"type":"integer","description":"Default TTL for async job results in seconds (default: 3600 = 1 hour)","default":3600,"minimum":1},"required_headers":{"type":"array","items":{"type":"string"},"description":"Headers that must be present on every request. Requests missing any of these headers are rejected with 400. Case-insensitive matching."},"logging_headers":{"type":"array","items":{"type":"string"},"description":"Headers to capture in log metadata. Values are extracted from incoming requests and stored in the metadata field of log entries."},"whitelisted_routes":{"type":"array","items":{"type":"string"},"description":"Routes that bypass auth middleware. Requests to these exact paths skip authentication checks."},"hide_deleted_virtual_keys_in_filters":{"type":"boolean","description":"When true, deleted virtual keys are omitted from logs and MCP logs filter data.","default":false},"allowed_headers":{"type":"array","items":{"type":"string"},"description":"Additional allowed headers for CORS and WebSocket"},"allow_direct_keys":{"type":"boolean","description":"Allow callers to bypass the registered key pool by supplying x-bf-direct-key: true and the provider's raw API key in an Authorization (Bearer), x-api-key, or x-goog-api-key header.","default":false},"mcp_agent_depth":{"type":"integer","minimum":1,"description":"DEPRECATED: Maximum depth for MCP agent mode tool execution. Use mcp.tool_manager_config.max_agent_depth.","default":10},"mcp_tool_execution_timeout":{"type":"integer","minimum":1,"description":"DEPRECATED: Timeout for individual MCP tool execution in seconds. Use mcp.tool_manager_config.tool_execution_timeout.","default":30},"mcp_code_mode_binding_level":{"type":"string","enum":["server","tool"],"description":"DEPRECATED: Code mode binding level for MCP tools. Use mcp.tool_manager_config.code_mode_binding_level."},"mcp_tool_sync_interval":{"type":"integer","minimum":0,"description":"DEPRECATED: Global tool sync interval in minutes (0 = default of 10 minutes). Use mcp.tool_sync_interval.","default":10},"mcp_disable_auto_tool_inject":{"type":"boolean","description":"DEPRECATED: When true, MCP tools are not automatically injected into requests. Use mcp.tool_manager_config.disable_auto_tool_inject.","default":false},"mcp_enable_temp_token_auth":{"type":"boolean","description":"When true, Bifrost mints and accepts scoped temp tokens for MCP per-user OAuth and per-user-headers auth pages so anonymous browser visitors can complete the flow without a dashboard session. User-mode flows are never minted regardless of this setting — they always require SSO login.","default":false},"routing_chain_max_depth":{"type":"integer","minimum":1,"description":"Maximum depth for routing rule chain evaluation","default":10},"mcp_external_client_url":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}],"description":"Public base URL Bifrost uses as the redirect_uri when acting as an OAuth client to upstream MCP servers (Notion, Jira, etc.). Set when Bifrost's callback endpoint is reached via a different URL than its server-side metadata. Supports env var syntax: \"env.MY_VAR\"."},"mcp_server_auth_mode":{"type":"string","enum":["headers","both","oauth"],"description":"How /mcp authenticates inbound MCP clients. 'headers' (default): VK/api-key/session headers only, discovery disabled. 'both': accepts header credentials and Bifrost-issued JWTs, discovery enabled. 'oauth': Bifrost JWTs only — WARNING: disables VK/header MCP access."},"oauth2_server_config":{"type":"object","properties":{"issuer_url":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}],"description":"Stable public URL advertised as the OAuth2 AS issuer in discovery documents and JWT iss claim. Required for multi-host deployments; single-host deployments can omit this (falls back to request Host header). Supports env var syntax: \"env.MY_VAR\"."},"auth_code_ttl":{"type":"integer","minimum":1,"maximum":900,"default":300,"description":"Lifetime of the single-use authorization code in seconds (default: 300, max: 900 = 15 minutes)."},"access_token_ttl":{"type":"integer","minimum":1,"default":600,"description":"Lifetime of the issued JWT Bearer token in seconds (default: 600)."},"disable_vk_identity":{"type":"boolean","default":false,"description":"When true, the OAuth consent flow no longer offers or accepts virtual-key identity and existing virtual-key grants lose access immediately (rejected at request time and unable to refresh). Honored only when an identity provider is configured. Only valid when mcp_server_auth_mode is 'oauth'."}},"additionalProperties":false,"description":"OAuth2 authorization server settings for /mcp. Only relevant when mcp_server_auth_mode is 'both' or 'oauth'."}},"additionalProperties":false},"framework":{"type":"object","properties":{"pricing":{"$ref":"#/$defs/pricing_config"}},"additionalProperties":false},"providers":{"type":"object","description":"AI provider configurations","properties":{"openai":{"$ref":"#/$defs/provider"},"anthropic":{"$ref":"#/$defs/provider"},"bedrock":{"$ref":"#/$defs/provider_with_bedrock_config"},"bedrock_mantle":{"$ref":"#/$defs/provider_with_bedrock_mantle_config"},"cohere":{"$ref":"#/$defs/provider"},"azure":{"$ref":"#/$defs/provider_with_azure_config"},"vertex":{"$ref":"#/$defs/provider_with_vertex_config"},"mistral":{"$ref":"#/$defs/provider"},"ollama":{"$ref":"#/$defs/provider_with_ollama_config"},"groq":{"$ref":"#/$defs/provider"},"gemini":{"$ref":"#/$defs/provider"},"opencode-go":{"$ref":"#/$defs/provider"},"opencode-zen":{"$ref":"#/$defs/provider"},"openrouter":{"$ref":"#/$defs/provider"},"sgl":{"$ref":"#/$defs/provider_with_sgl_config"},"parasail":{"$ref":"#/$defs/provider"},"perplexity":{"$ref":"#/$defs/provider"},"replicate":{"$ref":"#/$defs/provider_with_replicate_config"},"elevenlabs":{"$ref":"#/$defs/provider"},"cerebras":{"$ref":"#/$defs/provider"},"deepseek":{"$ref":"#/$defs/provider_with_deepseek_config"},"wafer":{"$ref":"#/$defs/provider"},"vllm":{"$ref":"#/$defs/provider_with_vllm_config"},"huggingface":{"$ref":"#/$defs/provider"},"fireworks":{"$ref":"#/$defs/provider_with_fireworks_config"},"nebius":{"$ref":"#/$defs/provider"},"xai":{"$ref":"#/$defs/provider"},"runway":{"$ref":"#/$defs/provider"},"runware":{"$ref":"#/$defs/provider"},"sarvam":{"$ref":"#/$defs/provider"}},"additionalProperties":true},"governance":{"type":"object","description":"Governance configuration for budgets, rate limits, customers, teams, virtual keys, and routing rules","properties":{"budgets":{"type":"array","description":"Budget configurations","items":{"type":"object","properties":{"id":{"type":"string","description":"Budget ID"},"max_limit":{"type":"number","description":"Maximum budget limit in dollars"},"reset_duration":{"type":"string","description":"Budget reset duration (e.g., '30s', '5m', '1h', '1d', '1w', '1M', '1Q', '1Y')"},"reset_config":{"type":"object","description":"Window settings the reset duration cannot express. Only valid when reset_duration is quarterly ('1Q').","properties":{"quarter_start_month":{"type":"integer","minimum":0,"maximum":12,"description":"First month of Q1 as 1-12, so a fiscal year opening in April (4) gives Apr-Jun / Jul-Sep / Oct-Dec / Jan-Mar. Omitted or 0 means January. Quarter boundaries repeat every three months, so only the value modulo 3 changes the reset dates: January, April, July and October all reset on calendar quarters and differ only in which quarter is labelled Q1."}},"additionalProperties":false},"current_usage":{"type":"number","description":"Seed value only: current usage in dollars, applied when the budget is first created. Runtime-owned thereafter - the value here is ignored on every subsequent sync so a config.json reload cannot overwrite live accounting.","default":0},"last_reset":{"type":"string","format":"date-time","description":"Seed value only: last time the budget was reset, applied when the budget is first created. Runtime-owned thereafter, like current_usage."},"virtual_key_id":{"type":"string","description":"ID of the virtual key this budget belongs to (mutually exclusive with provider_config_id)"},"provider_config_id":{"type":"integer","description":"ID of the provider config this budget belongs to (mutually exclusive with virtual_key_id)"},"model_config_id":{"type":"string","description":"ID of the model config this budget belongs to (mutually exclusive with the other owner IDs)"},"customer_id":{"type":"string","description":"ID of the customer this budget belongs to (mutually exclusive with the other owner IDs)"},"team_id":{"type":"string","description":"ID of the team this budget belongs to (mutually exclusive with virtual_key_id and provider_config_id)"},"calendar_aligned":{"type":"boolean","description":"Deprecated: set calendar_aligned on the owner (team / virtual key / access profile) instead. Kept for backward compatibility with older config.json files; ignored unless a reconciler promotes it to its owner.","default":false},"override_amount":{"type":"number","description":"Amount added to max_limit while an override is active","default":0},"override_mode":{"type":"string","enum":["","cycles","forever"],"description":"How long the override stays active: 'cycles' for a finite number of reset windows, 'forever' until explicitly removed, '' for no override","default":""},"override_cycles_remaining":{"type":"integer","description":"Reset windows the current override still covers, including the current one. Derived state: recomputed from override_anchor_reset, override_cycles_total and last_reset, so it is never the source of truth.","default":0},"override_cycles_total":{"type":"integer","description":"Number of reset windows the current finite override was granted for. Immutable for the life of a grant.","default":0},"override_anchor_reset":{"type":"string","format":"date-time","description":"Window boundary at which the current finite override was granted. Immutable for the life of a grant; absent when there is no finite override."}},"required":["id","max_limit","reset_duration"],"additionalProperties":false,"allOf":[{"if":{"required":["reset_config"]},"then":{"required":["reset_duration"],"properties":{"reset_duration":{"const":"1Q"}}}},{"if":{"not":{"properties":{"override_mode":{"enum":["cycles","forever"]}},"required":["override_mode"]}},"then":{"properties":{"override_amount":{"const":0},"override_cycles_remaining":{"const":0},"override_cycles_total":{"const":0}},"not":{"required":["override_anchor_reset"]}}},{"if":{"properties":{"override_mode":{"const":"cycles"}},"required":["override_mode"]},"then":{"required":["override_amount","override_cycles_remaining","override_cycles_total","override_anchor_reset"],"properties":{"override_amount":{"exclusiveMinimum":0},"override_cycles_remaining":{"minimum":1},"override_cycles_total":{"minimum":1}}}},{"if":{"properties":{"override_mode":{"const":"forever"}},"required":["override_mode"]},"then":{"required":["override_amount"],"properties":{"override_amount":{"exclusiveMinimum":0},"override_cycles_remaining":{"const":0},"override_cycles_total":{"const":0}},"not":{"required":["override_anchor_reset"]}}}]}},"rate_limits":{"type":"array","description":"Rate limit configurations","items":{"type":"object","properties":{"id":{"type":"string","description":"Rate limit ID"},"token_max_limit":{"type":"integer","description":"Maximum tokens allowed"},"token_reset_duration":{"type":"string","description":"Token reset duration (e.g., '30s', '5m', '1h', '1d', '1w', '1M', '1Y')"},"token_current_usage":{"type":"integer","description":"Seed value only: current token usage, applied when the rate limit is first created. Runtime-owned thereafter - the value here is ignored on every subsequent sync so a config.json reload cannot overwrite live counters.","default":0},"token_last_reset":{"type":"string","format":"date-time","description":"Seed value only: last time the token counter was reset, applied when the rate limit is first created. Runtime-owned thereafter, like token_current_usage."},"request_max_limit":{"type":"integer","description":"Maximum requests allowed"},"request_reset_duration":{"type":"string","description":"Request reset duration (e.g., '30s', '5m', '1h', '1d', '1w', '1M', '1Y')"},"request_current_usage":{"type":"integer","description":"Seed value only: current request usage, applied when the rate limit is first created. Runtime-owned thereafter - the value here is ignored on every subsequent sync so a config.json reload cannot overwrite live counters.","default":0},"request_last_reset":{"type":"string","format":"date-time","description":"Seed value only: last time the request counter was reset, applied when the rate limit is first created. Runtime-owned thereafter, like request_current_usage."},"calendar_aligned":{"type":"boolean","description":"Deprecated: set calendar_aligned on the owner (team / virtual key / access profile) instead. Kept for backward compatibility with older config.json files; ignored unless a reconciler promotes it to its owner.","default":false}},"required":["id"],"additionalProperties":false}},"customers":{"type":"array","description":"Customer configurations","items":{"type":"object","properties":{"id":{"type":"string","description":"Customer ID"},"name":{"type":"string","description":"Customer name"},"budgets":{"type":"array","description":"Inline budget configurations for this customer. Each entry must have a unique reset_duration. Mutually exclusive with budget_id.","items":{"$ref":"#/$defs/budget_line"}},"budget_id":{"type":"string","description":"Single budget reference (pre-declared in governance.budgets). Deprecated in favour of inline budgets."},"rate_limit_id":{"type":"string","description":"Associated rate limit ID"},"calendar_aligned":{"type":"boolean","description":"Snap the customer's budget and rate-limit reset windows to clean calendar boundaries (day, week, month, year)","default":false},"virtual_key_count":{"type":"integer","description":"Computed count of virtual keys associated with this customer","minimum":0,"readOnly":true}},"required":["id","name"],"not":{"required":["budgets","budget_id"]},"additionalProperties":false}},"teams":{"type":"array","description":"Team configurations","items":{"type":"object","properties":{"id":{"type":"string","description":"Team ID"},"name":{"type":"string","description":"Team name"},"source_id":{"type":"string","description":"Optional external source identifier (e.g. SCIM group ID) used to map this team to an upstream system. Unique when set."},"customer_id":{"type":"string","description":"Associated customer ID"},"budget_id":{"type":"string","description":"Associated budget ID"},"rate_limit_id":{"type":"string","description":"Associated rate limit ID"},"business_unit_id":{"type":"string","description":"Associated business unit ID"},"profile":{"type":"object","description":"Team profile data"},"config":{"type":"object","description":"Team configuration data"},"claims":{"type":"object","description":"Team claims data"},"calendar_aligned":{"type":"boolean","description":"Snap the team's budget and rate-limit reset windows to clean calendar boundaries (day, week, month, year)","default":false},"virtual_key_count":{"type":"integer","description":"Computed count of virtual keys associated with this team","minimum":0,"readOnly":true}},"required":["id","name"],"additionalProperties":false}},"business_units":{"type":"array","description":"Business unit configurations with optional team assignments","items":{"type":"object","properties":{"id":{"type":"string","description":"Business unit ID"},"name":{"type":"string","description":"Business unit name"},"budget_id":{"type":"string","description":"Associated budget ID"},"rate_limit_id":{"type":"string","description":"Associated rate limit ID"},"profile":{"type":"object","description":"Business unit profile data"},"config":{"type":"object","description":"Business unit configuration data"},"claims":{"type":"object","description":"Business unit claims data"},"team_ids":{"type":"array","description":"Team IDs to assign to this business unit","items":{"type":"string"}}},"required":["id","name"],"additionalProperties":false}},"roles":{"type":"array","description":"RBAC role definitions. Roles are created or updated on startup if the config hash changes; system roles and dashboard-created roles are never deleted.","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique role name"},"description":{"type":"string","description":"Human-readable description of the role"},"dac":{"type":"string","description":"Data Access Control scope: own-data (user's own records), team-data (user's team records), all-data (unrestricted). Defaults to all-data.","enum":["own-data","team-data","all-data"],"default":"all-data"},"access_profile":{"type":"string","description":"Name of the access profile (defined in access_profiles) to attach as the default for this role. Changing this field triggers a re-sync on next startup. Leave unset to let the dashboard manage the assignment."},"permissions":{"type":"array","description":"List of resource+operation permission pairs to grant to this role","items":{"type":"object","properties":{"resource":{"type":"string","description":"Permission resource (e.g. Logs, VirtualKeys, Users, ModelProvider, Metrics, Guardrails, AccessProfiles, MCPToolGroups, Roles, BusinessUnits, AuditLogs, Billing)"},"operation":{"type":"string","description":"Permission operation (e.g. View, Create, Update, Delete, Download, RunInference, ManageInference, ViewInference)"}},"required":["resource","operation"],"additionalProperties":false}}},"required":["name"],"additionalProperties":false}},"virtual_keys":{"type":"array","description":"Virtual key configurations","items":{"type":"object","properties":{"id":{"type":"string","description":"Virtual key ID"},"name":{"type":"string","description":"Virtual key name"},"description":{"type":"string","description":"Virtual key description"},"value":{"type":"string","description":"The virtual key value. A literal, or an \"env.VAR\"/\"vault.path\" reference resolved to plaintext at load time."},"is_active":{"type":"boolean","description":"Whether the virtual key is active","default":true},"expires_at":{"type":"string","format":"date-time","description":"Optional expiry timestamp (RFC3339). Once passed, requests using this virtual key are rejected. Omit for a key that never expires."},"calendar_aligned":{"type":"boolean","description":"Snap all budget resets to calendar boundaries (day, week, month, year)","default":false},"team_id":{"type":"string","description":"Associated team ID (mutually exclusive with customer_id)"},"customer_id":{"type":"string","description":"Associated customer ID (mutually exclusive with team_id)"},"access_profile_id":{"type":"integer","minimum":1,"description":"Associated access profile ID for direct access profile assignment (mutually exclusive with team_id and customer_id)"},"is_access_profile_managed":{"type":"boolean","description":"Read-only, server-computed flag: true when the virtual key is governed by an access profile. Never persisted and ignored if set in config.json.","readOnly":true},"rate_limit_id":{"type":"string","description":"Associated rate limit ID"},"provider_configs":{"type":"array","description":"Provider configurations for this virtual key (empty means no providers allowed, deny-by-default)","items":{"$ref":"#/$defs/virtual_key_provider_config"}},"mcp_configs":{"type":"array","description":"MCP configurations for this virtual key (empty array means no MCP tools allowed, deny-by-default)","items":{"$ref":"#/$defs/virtual_key_mcp_config"}}},"required":["id","name"],"additionalProperties":false}},"routing_rules":{"type":"array","description":"Routing rules for dynamic provider/model selection based on CEL expressions","items":{"$ref":"#/$defs/routing_rule"}},"pricing_overrides":{"type":"array","description":"Scoped pricing overrides applied at runtime by the model catalog","items":{"$ref":"#/$defs/provider_pricing_override"}},"auth_config":{"$ref":"#/$defs/auth_config"},"complexity_analyzer_config":{"$ref":"#/$defs/complexity_analyzer_config"},"model_configs":{"type":"array","description":"Per-model rate limit and budget configurations","items":{"type":"object","properties":{"id":{"type":"string","description":"Model config ID"},"model_name":{"type":"string","description":"Model name to apply the configuration to"},"provider":{"type":"string","description":"Optional provider name to scope this config"},"scope":{"type":"string","description":"Scope where this config applies: \"global\" (default) or \"virtual_key\"","default":"global"},"scope_id":{"type":"string","description":"Target entity ID for non-global scopes (e.g. virtual key ID). Required when scope != \"global\""},"calendar_aligned":{"type":"boolean","description":"Snap this config's budget resets to calendar boundaries (e.g. a monthly budget resets on the 1st) rather than rolling windows. For virtual_key-scoped configs it inherits the virtual key's setting.","default":false},"budget_id":{"type":"string","description":"Deprecated — single budget reference. Use budget_ids instead."},"budget_ids":{"type":"array","description":"List of budget IDs (from governance.budgets) to associate with this model config. Supports multiple budgets (e.g. daily + monthly). Replaces budget_id.","items":{"type":"string"}},"rate_limit_id":{"type":"string","description":"Rate limit ID to associate with this model"}},"required":["id","model_name"],"additionalProperties":false}},"providers":{"type":"array","description":"Provider-level governance configurations","items":{"type":"object","properties":{"id":{"type":"string","description":"Provider row ID"},"name":{"type":"string","description":"Provider name"},"description":{"type":"string","description":"Operator-facing provider description"},"budget_id":{"type":"string","description":"Associated budget ID"},"rate_limit_id":{"type":"string","description":"Associated rate limit ID"},"send_back_raw_request":{"type":"boolean","description":"Include raw request in BifrostResponse"},"send_back_raw_response":{"type":"boolean","description":"Include raw response in BifrostResponse"},"store_raw_request_response":{"type":"boolean","description":"Capture raw request/response for internal logging only; strip from API responses returned to clients (default: false)"},"network_config":{"$ref":"#/$defs/network_config"},"proxy_config":{"$ref":"#/$defs/proxy_config"},"custom_provider_config":{"$ref":"#/$defs/custom_provider_config"},"concurrency_and_buffer_size":{"$ref":"#/$defs/concurrency_and_buffer_size"},"openai_config":{"$ref":"#/$defs/openai_config"}},"required":["name"]}}},"additionalProperties":false},"alerting":{"type":"object","description":"Enterprise alerting configuration for governance-backed alert channels, rules, history retention, and outbound webhook URL validation.","properties":{"history_retention_days":{"type":"integer","minimum":0,"default":365,"description":"Number of days to retain alert history. Omit to use the default. Set to 0 to disable retention pruning."},"webhook_network":{"type":"object","description":"Outbound URL validation controls for Slack, Microsoft Teams, and generic webhook alert channels.","properties":{"allow_http":{"type":"boolean","default":false,"description":"Allow alert webhooks to use http URLs. By default only https URLs are accepted."},"allow_private_network":{"type":"boolean","default":false,"description":"Allow alert webhooks to target private or loopback network addresses. Useful for local testing."}},"additionalProperties":false},"channels":{"type":"array","description":"Declarative alert channels synced into the enterprise config store at startup.","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable alert channel ID."},"name":{"type":"string","description":"Operator-facing alert channel name."},"description":{"type":"string","description":"Optional alert channel description."},"type":{"type":"string","enum":["slack","microsoft_teams","pagerduty","webhook"],"description":"Alert channel type."},"enabled":{"type":"boolean","description":"Whether this alert channel can receive notifications."},"cooldown_seconds":{"type":"integer","minimum":0,"description":"Minimum seconds between sends for this channel. Set to 0 for no channel-level cooldown."},"config":{"type":"object","description":"Channel-specific delivery configuration. Slack and Microsoft Teams use webhook_url or url. PagerDuty uses routing_key or integration_key. Generic webhook uses url or webhook_url, plus optional string headers.","additionalProperties":true}},"required":["id","name","type","enabled"],"allOf":[{"if":{"properties":{"type":{"const":"slack"}},"required":["type"]},"then":{"properties":{"config":{"type":"object","properties":{"webhook_url":{"type":"string","minLength":1,"anyOf":[{"format":"uri"},{"pattern":"^env\\.[A-Za-z_][A-Za-z0-9_]*$"}],"description":"Slack incoming webhook URL. Supports env.VAR_NAME references."},"url":{"type":"string","minLength":1,"anyOf":[{"format":"uri"},{"pattern":"^env\\.[A-Za-z_][A-Za-z0-9_]*$"}],"description":"Alias for webhook_url. Supports env.VAR_NAME references."}},"oneOf":[{"required":["webhook_url"]},{"required":["url"]}],"additionalProperties":true}},"required":["config"]}},{"if":{"properties":{"type":{"const":"microsoft_teams"}},"required":["type"]},"then":{"properties":{"config":{"type":"object","properties":{"webhook_url":{"type":"string","minLength":1,"anyOf":[{"format":"uri"},{"pattern":"^env\\.[A-Za-z_][A-Za-z0-9_]*$"}],"description":"Microsoft Teams incoming webhook or workflow URL. Supports env.VAR_NAME references."},"url":{"type":"string","minLength":1,"anyOf":[{"format":"uri"},{"pattern":"^env\\.[A-Za-z_][A-Za-z0-9_]*$"}],"description":"Alias for webhook_url. Supports env.VAR_NAME references."}},"oneOf":[{"required":["webhook_url"]},{"required":["url"]}],"additionalProperties":true}},"required":["config"]}},{"if":{"properties":{"type":{"const":"pagerduty"}},"required":["type"]},"then":{"properties":{"config":{"type":"object","properties":{"routing_key":{"type":"string","minLength":1,"description":"PagerDuty Events API v2 integration key. Supports env.VAR_NAME references."},"integration_key":{"type":"string","minLength":1,"description":"Alias for routing_key. Supports env.VAR_NAME references."}},"oneOf":[{"required":["routing_key"]},{"required":["integration_key"]}],"additionalProperties":true}},"required":["config"]}},{"if":{"properties":{"type":{"const":"webhook"}},"required":["type"]},"then":{"properties":{"config":{"type":"object","properties":{"url":{"type":"string","minLength":1,"anyOf":[{"format":"uri"},{"pattern":"^env\\.[A-Za-z_][A-Za-z0-9_]*$"}],"description":"Generic webhook URL. Supports env.VAR_NAME references."},"webhook_url":{"type":"string","minLength":1,"anyOf":[{"format":"uri"},{"pattern":"^env\\.[A-Za-z_][A-Za-z0-9_]*$"}],"description":"Alias for url. Supports env.VAR_NAME references."},"headers":{"type":"object","description":"Optional headers to send with the webhook request. Values support env.VAR_NAME references. Sensitive hop-by-hop headers are ignored at send time.","additionalProperties":{"type":"string"}}},"oneOf":[{"required":["url"]},{"required":["webhook_url"]}],"additionalProperties":true}},"required":["config"]}}],"additionalProperties":false}},"rules":{"type":"array","description":"Declarative alert rules synced into the enterprise config store at startup.","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable alert rule ID."},"name":{"type":"string","description":"Operator-facing alert rule name."},"description":{"type":"string","description":"Optional alert rule description."},"enabled":{"type":"boolean","description":"Whether this alert rule is evaluated."},"scope_type":{"type":"string","enum":["virtual_key","team","customer"],"description":"Governance owner scope to evaluate."},"scope_id":{"type":"string","description":"ID of the virtual key, team, or customer referenced by scope_type."},"cel_expression":{"type":"string","description":"CEL expression evaluated against alerting usage variables such as budget_usage_percent, budget_spent, request_usage, token_usage, and rate-limit percentages."},"query":{"type":"object","description":"Optional UI query-builder representation of cel_expression.","additionalProperties":true},"cooldown_seconds":{"type":"integer","minimum":0,"default":60,"description":"Minimum seconds between notifications for this rule. Omit to use the default. Set to 0 for no rule cooldown."},"notify_once_per_reset_cycle":{"type":"boolean","default":false,"description":"When true, notify at most once per matched budget or rate-limit reset cycle. This overrides rule cooldown to 0."},"channel_ids":{"type":"array","description":"IDs of alert channels to notify when this rule matches.","items":{"type":"string","minLength":1},"minItems":1},"target_type":{"type":"string","enum":["budget"],"description":"Optional explicit target type. Use budget with target_id to evaluate one specific budget; omit to evaluate all budgets for the scope."},"target_id":{"type":"string","description":"ID of the explicit target referenced by target_type."}},"required":["id","name","enabled","scope_type","scope_id","cel_expression","channel_ids"],"allOf":[{"if":{"required":["target_type"]},"then":{"required":["target_id"]}},{"if":{"required":["target_id"]},"then":{"required":["target_type"]}}],"additionalProperties":false}}},"additionalProperties":false},"webhooks":{"type":"array","description":"Webhook endpoint declarations synced into the database at startup, reconciled by name.","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable endpoint ID. Generated when omitted."},"name":{"type":"string","description":"Unique endpoint name."},"url":{"type":"string","description":"Delivery URL. HTTPS required unless allow_private_network is set; credentials and fragments are rejected; link-local and metadata addresses are always blocked."},"secret":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}],"description":"Signing secret in the whsec_ format. Best supplied as an env reference (\"env.MY_VAR\"); generated when omitted."},"events":{"type":"array","items":{"type":"string","enum":["async_job.completed","async_job.failed"]},"description":"Events this endpoint subscribes to."},"headers":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]},"description":"Custom headers sent with every delivery (e.g. an Authorization value the receiver requires). Values support env var syntax (\"env.MY_VAR\") and are encrypted at rest. Reserved delivery headers (webhook-id, webhook-timestamp, webhook-signature, content-type, etc.) cannot be overridden."},"include_response":{"type":"boolean","default":false,"description":"Inline the job response into delivery payloads, capped by max_response_payload_kbs."},"allow_private_network":{"type":"boolean","default":false,"description":"Permit private-network receivers and plain http. Link-local and metadata addresses stay blocked."},"disabled":{"type":"boolean","default":false,"description":"Register the endpoint without delivering to it."},"max_retries":{"type":"integer","minimum":1,"description":"How many times a failed delivery is retried after its first attempt (default: 4)."},"retry_backoff_initial_seconds":{"type":"integer","minimum":1,"description":"Delay before the first retry in seconds; each further retry doubles it (default: 30)."},"retry_backoff_max_seconds":{"type":"integer","minimum":1,"description":"Cap on the per-retry delay in seconds (default: 1800)."},"attempt_timeout_seconds":{"type":"integer","minimum":1,"description":"End-to-end bound for one delivery attempt in seconds (default: 10)."},"max_response_payload_kbs":{"type":"integer","minimum":1,"description":"Cap for inlined response/error payloads in KB when include_response is set (default: 256). Oversized values are omitted and flagged with response_omitted/error_omitted."},"max_concurrent_deliveries":{"type":"integer","minimum":1,"description":"Concurrent in-flight deliveries to this endpoint per node (default: 10)."}},"required":["name","url","events"],"additionalProperties":false}},"mcp":{"type":"object","description":"Model Context Protocol configuration","properties":{"client_configs":{"type":"array","items":{"$ref":"#/$defs/mcp_client_config"},"description":"MCP client configurations"},"tool_manager_config":{"$ref":"#/$defs/mcp_tool_manager_config"},"tool_sync_interval":{"description":"Global default interval for syncing tools from MCP servers (Go duration string in whole minutes, e.g. '10m', '1h', '1h30m'; '0s' uses the runtime default of 10 minutes). Legacy integer nanoseconds are also accepted and must be a whole number of minutes.","oneOf":[{"type":"string","pattern":"^(?:0|0s|0m|0h|(?:\\d+h)?\\d+m|\\d+h)$"},{"type":"integer","minimum":0,"multipleOf":60000000000}]},"tool_groups":{"type":"array","description":"Enterprise MCP tool groups with optional governance associations","items":{"$ref":"#/$defs/mcp_tool_group_config"}}},"additionalProperties":false},"vector_store":{"type":"object","description":"Vector store configuration for caching","properties":{"enabled":{"type":"boolean","description":"Enable vector store"},"type":{"type":"string","enum":["weaviate","redis","qdrant","pinecone"],"description":"Vector store type (use \"redis\" for Redis or Valkey-compatible endpoints)"},"config":{"anyOf":[{"if":{"properties":{"type":{"const":"weaviate"}}},"then":{"$ref":"#/$defs/weaviate_config"}},{"if":{"properties":{"type":{"const":"redis"}}},"then":{"$ref":"#/$defs/redis_config"}},{"if":{"properties":{"type":{"const":"qdrant"}}},"then":{"$ref":"#/$defs/qdrant_config"}},{"if":{"properties":{"type":{"const":"pinecone"}}},"then":{"$ref":"#/$defs/pinecone_config"}}]}},"additionalProperties":false},"config_store":{"type":"object","description":"Configuration store settings","properties":{"enabled":{"type":"boolean","description":"Enable configuration store"},"type":{"type":"string","enum":["sqlite","postgres"],"description":"Configuration store type"},"config":{"anyOf":[{"if":{"properties":{"type":{"const":"sqlite"}}},"then":{"type":"object","properties":{"path":{"type":"string","description":"Database file path"}},"required":["path"],"additionalProperties":false}},{"if":{"properties":{"type":{"const":"postgres"}}},"then":{"type":"object","properties":{"host":{"type":"string","description":"Database host"},"port":{"type":"string","description":"Database port"},"user":{"type":"string","description":"Database user"},"password":{"type":"string","description":"Database password. Use password_command instead when the password must be generated dynamically."},"password_command":{"type":"object","description":"Command executed without a shell to produce the database password on stdout for each new physical connection.","properties":{"command":{"type":"string","minLength":1,"pattern":"^\\S+$","description":"Executable path or name to run"},"args":{"type":"array","description":"Arguments passed directly to the executable","items":{"type":"string"},"default":[]},"timeout":{"type":"string","description":"Command timeout as a Go duration string (default: 10s)","pattern":"^[1-9][0-9]*(ns|us|µs|ms|s|m|h)$","default":"10s"},"cache_ttl":{"type":"string","description":"How long a resolved password is reused across new physical connections (Go duration string, default 60s). Without caching the command runs on every new connection. Keep this below the credential's validity window: a rotation shorter than the TTL will fail connections until the cached value expires.","pattern":"^[1-9][0-9]*(ns|us|µs|ms|s|m|h)$","default":"60s"}},"required":["command"],"additionalProperties":false},"db_name":{"type":"string","description":"Database name"},"ssl_mode":{"type":"string","description":"Database SSL mode"},"max_idle_conns":{"type":"integer","description":"Maximum number of idle connections in the pool (default: 5)","minimum":0,"default":5},"max_open_conns":{"type":"integer","description":"Maximum number of open connections to the database (default: 50)","minimum":2,"default":50},"conn_max_lifetime":{"type":"string","description":"Maximum lifetime for physical database connections as a Go duration string","pattern":"^[1-9][0-9]*(ns|us|µs|ms|s|m|h)$"},"conn_max_idle_time":{"type":"string","description":"How long an idle physical connection is kept before being closed (Go duration string, default 5m). Without this the idle cap alone controls pool size, so bursts above max_idle_conns close and reopen connections repeatedly. Note the logs store and config store open separate pools, so a pod's connection ceiling is the sum of their max_open_conns.","pattern":"^[1-9][0-9]*(ns|us|µs|ms|s|m|h)$","default":"5m"}},"required":["host","port","user","db_name","ssl_mode"],"oneOf":[{"required":["password"]},{"required":["password_command"]}],"additionalProperties":false}}]},"vault_store":{"type":"object","description":"Vault store for external secret management. Fields stored as vault.<path> or vault.<path>#<jsonKey> references are resolved through the configured backend.","properties":{"enabled":{"type":"boolean","description":"Enable vault integration"},"type":{"type":"string","enum":["aws-secrets-manager","gcp-secret-manager","hashicorp-vault"],"description":"Vault backend type"},"prefix":{"type":"string","description":"Path prefix applied to every secret (default: bifrost)"},"access_mode":{"type":"string","enum":["read_only","read_and_write"],"description":"read_only resolves existing refs only; read_and_write also auto-stores plaintext fields and deletes owned secrets. Defaults to read_only."},"aws":{"type":"object","description":"AWS Secrets Manager configuration","properties":{"region":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]},"access_key_id":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]},"secret_access_key":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]},"session_token":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]},"role_arn":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]},"kms_key_id":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]}},"additionalProperties":false},"gcp":{"type":"object","description":"GCP Secret Manager configuration","properties":{"project_id":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]},"credentials_json":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]}},"additionalProperties":false},"hashicorp":{"type":"object","description":"HashiCorp Vault (KV v2) configuration","properties":{"address":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]},"token":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]},"namespace":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]},"mount_path":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]},"role_id":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]},"secret_id":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"logs_store":{"type":"object","description":"Logs store settings","properties":{"enabled":{"type":"boolean","description":"Enable logs store"},"type":{"type":"string","enum":["sqlite","postgres","clickhouse"],"description":"Logs store type"},"config":{"type":"object","oneOf":[{"if":{"properties":{"../type":{"const":"sqlite"}}},"then":{"properties":{"path":{"type":"string","description":"Database file path"}},"required":["path"],"additionalProperties":false}},{"if":{"properties":{"../type":{"const":"postgres"}}},"then":{"properties":{"host":{"type":"string","description":"Database host"},"port":{"type":"string","description":"Database port"},"user":{"type":"string","description":"Database user"},"password":{"type":"string","description":"Database password. Use password_command instead when the password must be generated dynamically."},"password_command":{"type":"object","description":"Command executed without a shell to produce the database password on stdout for each new physical connection.","properties":{"command":{"type":"string","minLength":1,"pattern":"^\\S+$","description":"Executable path or name to run"},"args":{"type":"array","description":"Arguments passed directly to the executable","items":{"type":"string"},"default":[]},"timeout":{"type":"string","description":"Command timeout as a Go duration string (default: 10s)","pattern":"^[1-9][0-9]*(ns|us|µs|ms|s|m|h)$","default":"10s"},"cache_ttl":{"type":"string","description":"How long a resolved password is reused across new physical connections (Go duration string, default 60s). Without caching the command runs on every new connection. Keep this below the credential's validity window: a rotation shorter than the TTL will fail connections until the cached value expires.","pattern":"^[1-9][0-9]*(ns|us|µs|ms|s|m|h)$","default":"60s"}},"required":["command"],"additionalProperties":false},"db_name":{"type":"string","description":"Database name"},"ssl_mode":{"type":"string","description":"Database SSL mode"},"max_idle_conns":{"type":"integer","description":"Maximum number of idle connections in the pool (default: 5)","minimum":0,"default":5},"max_open_conns":{"type":"integer","description":"Maximum number of open connections to the database (default: 50)","minimum":2,"default":50},"matview_refresh_interval":{"type":"string","description":"How often to refresh dashboard materialized views. Go duration string (e.g. '1m', '5m', '1h'). Default 1m. Raise this when matview refresh CPU cost is material on the database instance. Positive values below 5s are clamped up to 5s. Set 'off' or a zero duration (e.g. '0s') to disable materialized-view maintenance entirely; dashboard queries fall back to the raw tables.","pattern":"^(off|[0-9]+(ns|us|µs|ms|s|m|h))$","default":"1m"},"matview_refresh_timeout":{"type":"string","description":"Maximum time for a single materialized view refresh pass. Go duration string, min 30s, max 30m. Unset derives a budget from matview_refresh_interval (5x the interval, at least 5m). A refresh holds a pooled connection and a session advisory lock across every view, so without a bound one stuck refresh leaves the views permanently stale across all replicas.","pattern":"^[1-9][0-9]*(ns|us|µs|ms|s|m|h)$"},"conn_max_lifetime":{"type":"string","description":"Maximum lifetime for physical database connections as a Go duration string","pattern":"^[1-9][0-9]*(ns|us|µs|ms|s|m|h)$"},"conn_max_idle_time":{"type":"string","description":"How long an idle physical connection is kept before being closed (Go duration string, default 5m). Without this the idle cap alone controls pool size, so bursts above max_idle_conns close and reopen connections repeatedly. Note the logs store and config store open separate pools, so a pod's connection ceiling is the sum of their max_open_conns.","pattern":"^[1-9][0-9]*(ns|us|µs|ms|s|m|h)$","default":"5m"}},"required":["host","port","user","db_name","ssl_mode"],"oneOf":[{"required":["password"]},{"required":["password_command"]}],"additionalProperties":false}},{"if":{"properties":{"../type":{"const":"clickhouse"}}},"then":{"properties":{"host":{"type":"string","description":"ClickHouse host"},"port":{"type":"string","description":"ClickHouse port. Defaults by protocol: native 9000 (9440 TLS), http 8123 (8443 TLS)"},"database":{"type":"string","description":"ClickHouse database name (default: default)"},"username":{"type":"string","description":"ClickHouse username"},"password":{"type":"string","description":"ClickHouse password"},"protocol":{"type":"string","enum":["native","http"],"description":"ClickHouse wire protocol (default: native)"},"secure":{"type":"boolean","description":"Enable TLS (native: secure=true; http: switches to https)","default":false},"dial_timeout":{"type":"integer","description":"Connection dial timeout in milliseconds (default: 10000)","minimum":1,"default":10000},"cluster":{"type":"string","description":"Optional cluster name; when set, DDL runs ON CLUSTER with replicated table engines"}},"required":["host"],"additionalProperties":false}}]},"writer":{"type":"object","description":"Async logging writer queue and batch tuning. Omitted fields use runtime defaults.","properties":{"max_batch_size":{"type":"integer","description":"Maximum number of log entries to batch before flushing (default: 1000)","minimum":1,"default":1000},"batch_interval":{"type":"string","description":"Maximum time to wait before flushing a partial batch as a Go duration string (default: 5s)","pattern":"^[1-9][0-9]*(ns|us|µs|ms|s|m|h)$","default":"5s"},"max_batch_bytes":{"type":"integer","description":"Approximate byte-size ceiling for a batch before flushing (default: 314572800)","minimum":1,"default":314572800},"write_queue_capacity":{"type":"integer","description":"Buffered queue capacity for pending log writes (default: 10000)","minimum":1,"default":10000},"deferred_usage_concurrency":{"type":"integer","description":"Maximum concurrent deferred usage database updates (default: 5)","minimum":1,"default":5}},"additionalProperties":false},"object_storage":{"type":"object","description":"Optional object storage for offloading LLM and MCP log payloads. When configured, full payloads are stored in S3/GCS while the DB keeps lightweight index data. MCP log rows keep dashboard/table fields plus a 200-character input preview.","properties":{"type":{"type":"string","enum":["s3","gcs"],"description":"Object storage backend type"},"bucket":{"anyOf":[{"type":"string","minLength":1},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"Bucket name. Supports env var reference (e.g. env.S3_BUCKET) or a structured secret reference."},"prefix":{"type":"string","description":"Key prefix for stored objects (default: bifrost)","default":"bifrost"},"compress":{"type":"boolean","description":"Enable gzip compression for stored objects. Default: false","default":false}},"required":["type","bucket"],"if":{"properties":{"type":{"const":"s3"}}},"then":{"properties":{"type":true,"bucket":true,"prefix":true,"region":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"AWS region. Supports env var reference"},"endpoint":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"Custom S3-compatible endpoint for MinIO/R2. Supports env var reference"},"access_key_id":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"AWS access key ID. Omit to use default credential chain (instance role, env vars, etc.). Supports env var reference"},"secret_access_key":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"AWS secret access key. Supports env var reference"},"session_token":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"AWS session token for STS temporary credentials. Supports env var reference"},"role_arn":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"AWS IAM role ARN for STS AssumeRole. Works with static creds or instance role. Supports env var reference"},"force_path_style":{"type":"boolean","description":"Use path-style URLs for S3 (required for MinIO). Default: false","default":false},"compress":true},"dependentRequired":{"access_key_id":["secret_access_key"],"secret_access_key":["access_key_id"],"session_token":["access_key_id","secret_access_key"]},"additionalProperties":false},"else":{"properties":{"type":true,"bucket":true,"prefix":true,"credentials_json":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"GCP service account credentials JSON or file path. Omit to use Application Default Credentials. Supports env var reference"},"credentials":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"Deprecated: use credentials_json. Kept for backwards compatibility."},"project_id":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"GCP project ID override. Supports env var reference"},"compress":true},"additionalProperties":false}},"object_storage_exclude_fields":{"type":"array","description":"LLM log payload field DB column names that should NOT be offloaded to object storage and must remain in the database (e.g. output_message, input_history, raw_request, raw_response). Unknown names are ignored. MCP logs always offload the full log and keep dashboard/table fields plus a 200-character input preview in the database.","items":{"type":"string"}},"retention_days":{"type":"integer","minimum":0,"description":"Days to retain log entries. 0 disables retention-based cleanup."}},"additionalProperties":false},"skills_registry":{"type":"object","description":"Declarative Skills Repository definitions reconciled from config.json","properties":{"enabled":{"type":"boolean","description":"Whether config-defined skills should be reconciled at startup"},"skills":{"type":"array","description":"Skills to create or update from config.json","items":{"type":"object","required":["name","description","skill_md_body","version"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"license":{"type":"string"},"compatibility":{"type":"string"},"allowed_tools":{"type":"string"},"extra_frontmatter":{"type":"object","additionalProperties":true},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"skill_md_body":{"type":"string"},"version":{"type":"string"},"files":{"type":"array","items":{"type":"object","required":["path","source_type"],"properties":{"path":{"type":"string"},"source_type":{"type":"string","enum":["text","url","dataurl"]},"url":{"type":"string"},"content":{"type":"string"},"dataurl":{"type":"string"}},"additionalProperties":false,"oneOf":[{"properties":{"source_type":{"const":"url"}},"required":["url"]},{"properties":{"source_type":{"const":"text"}},"required":["content"]},{"properties":{"source_type":{"const":"dataurl"}},"required":["dataurl"]}]}}},"additionalProperties":false}}},"additionalProperties":false},"plugins":{"type":"array","description":"Plugins configuration","items":{"type":"object","required":["enabled","name"],"properties":{"enabled":{"type":"boolean","description":"Enable plugins"},"name":{"type":"string","description":"Name of the plugin (built-in: telemetry, prompts, logging, governance, maxim, semantic_cache, otel, or custom plugin name)"},"config":{"type":"object","description":"Configuration for the plugin"},"path":{"type":"string","description":"Path to the plugin (optional, required for dynamic plugins)","optional":true},"version":{"type":"integer","minimum":1,"maximum":32767,"description":"DB-Backed Only. Version metadata persisted on TablePlugin (default: 1). In DB-backed sync, version metadata is considered for plugin replacement/reload decisions.","optional":true,"default":1},"placement":{"type":"string","enum":["pre_builtin","post_builtin","builtin"],"description":"DB-Backed Only. Whether this plugin runs before, after, or as a built-in. Default: post_builtin. Ignored in config.json.","optional":true,"default":"post_builtin"},"order":{"type":"integer","description":"DB-Backed Only. Position within placement group. Lower values execute earlier. Default: 0. Ignored in config.json.","optional":true,"default":0},"semaphore_size":{"type":"integer","description":"For plugins that implement ObservabilityPlugin (receive completed traces): cap on how many traces may be injected into this plugin concurrently. A saturated cap causes new traces to be dropped for this plugin only, not process-wide. Ignored by plugins that don't receive traces (default: 10000).","default":10000,"minimum":1},"inject_timeout":{"type":"string","description":"For plugins that implement ObservabilityPlugin: timeout for a single Inject call from the tracer, as a Go duration string (default: 5s). Bounds how long a hung connector can hold a concurrency-budget slot. Ignored by plugins that don't receive traces.","pattern":"^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$","default":"5s"}},"allOf":[{"if":{"properties":{"name":{"const":"telemetry"}}},"then":{"required":["config"],"properties":{"config":{"type":"object","description":"Configuration for the telemetry plugin (Prometheus metrics)","properties":{"custom_labels":{"type":"array","items":{"type":"string"},"description":"Custom labels to add to Prometheus metrics"},"push_gateway":{"type":"object","description":"Configuration for pushing metrics to a Prometheus Push Gateway for multi-node cluster deployments","properties":{"enabled":{"type":"boolean","description":"Whether to enable pushing metrics to the Push Gateway","default":false},"push_gateway_url":{"type":"string","description":"URL of the Prometheus Push Gateway (e.g., http://pushgateway:9091)","format":"uri"},"job_name":{"type":"string","description":"Job label for pushed metrics","default":"bifrost"},"instance_id":{"type":"string","description":"Instance identifier for grouping metrics. If not set, hostname is used automatically."},"push_interval":{"type":"integer","description":"How often to push metrics in seconds","default":15,"minimum":1,"maximum":300},"basic_auth":{"type":"object","description":"Basic authentication credentials for the Push Gateway","properties":{"username":{"type":"string","description":"Username for basic authentication"},"password":{"type":"string","description":"Password for basic authentication"}},"required":["username","password"]}},"required":["push_gateway_url"]}},"additionalProperties":false}}}},{"if":{"properties":{"name":{"const":"logging"}}},"then":{"required":["config"],"properties":{"config":{"type":"object","description":"Configuration for the logging plugin","properties":{"disable_content_logging":{"type":"boolean","description":"Disable logging of request and response content"},"retain_content_in_object_storage":{"type":"boolean","description":"When content logging is disabled, offload the full content to object storage as hidden instead of dropping it. Hidden content is never served back through the API/UI. Requires object storage to be configured on the logs store.","default":false},"allow_per_request_content_storage_override":{"type":"boolean","description":"Allow individual requests to override content storage via the x-bf-disable-content-logging header or context key, and to opt in to raw-byte persistence in logs via x-bf-store-raw-request-response. When false (default), per-request storage overrides are ignored. Does not control sending raw bytes back to callers — see allow_per_request_raw_override.","default":false},"logging_headers":{"type":"array","items":{"type":"string"},"description":"List of headers to capture in log metadata"}},"additionalProperties":false}}}},{"if":{"properties":{"name":{"const":"governance"}}},"then":{"required":["config"],"properties":{"config":{"type":"object","description":"Configuration for the governance plugin","properties":{"is_vk_mandatory":{"type":"boolean","description":"Whether virtual key (x-bf-vk header) is mandatory for all requests"},"required_headers":{"type":"array","items":{"type":"string"},"description":"List of headers that must be present in requests"},"is_enterprise":{"type":"boolean","description":"Enable enterprise mode features"}},"additionalProperties":false}}}},{"if":{"properties":{"name":{"const":"maxim"}}},"then":{"required":["config"],"properties":{"config":{"type":"object","description":"Configuration for the Maxim SDK integration plugin","properties":{"api_key":{"type":"string","description":"API key for Maxim SDK authentication"},"log_repo_id":{"type":"string","description":"Optional default ID for the Maxim logger instance"},"request_headers":{"type":"array","items":{"type":"string"},"description":"Request header name patterns (exact or wildcard like x-custom-*) to capture and attach as trace tags. Note: * captures all headers including sensitive ones like Authorization."}},"required":["api_key"],"additionalProperties":false}}}},{"if":{"properties":{"name":{"const":"semantic_cache"}}},"then":{"required":["config"],"properties":{"config":{"type":"object","description":"Configuration for the semantic cache plugin","properties":{"provider":{"type":"string","minLength":1,"description":"Provider to use for generating embeddings. Required for semantic search; omit it for direct hash mode with dimension: 1.","enum":["openai","anthropic","gemini","bedrock","bedrock_mantle","azure","cohere","mistral","groq","ollama","openrouter","vertex","cerebras","deepseek","vllm","parasail","perplexity","replicate","sgl","huggingface"]},"embedding_model":{"type":"string","description":"Model to use for generating embeddings in provider-backed semantic caching. Required when provider is set and not allowed in direct-only mode."},"ttl":{"description":"Time-to-live for cached responses (supports duration strings like '5m', '1h' or seconds as number, default: 5min)","oneOf":[{"type":"string","pattern":"^[0-9]+(ns|us|µs|ms|s|m|h)$"},{"type":"integer","minimum":0}]},"threshold":{"type":"number","description":"Cosine similarity threshold for semantic matching (default: 0.8)","minimum":0,"maximum":1},"vector_store_namespace":{"type":"string","description":"Namespace for vector store (optional)"},"dimension":{"type":"integer","description":"Dimension for vector store embeddings. Use 1 for direct (hash-based) caching without an embedding provider.","minimum":1},"default_cache_key":{"type":"string","description":"Default cache key used when no per-request key is provided. When set, all requests without an explicit x-bf-cache-key header will use this value and be cached automatically."},"conversation_history_threshold":{"type":"integer","description":"Skip caching for requests with more than this number of messages in conversation history (default: 3)","minimum":0},"cache_by_model":{"type":"boolean","description":"Include model in cache key (default: true)"},"cache_by_provider":{"type":"boolean","description":"Include provider in cache key (default: true)"},"exclude_system_prompt":{"type":"boolean","description":"Exclude system prompt in cache key (default: false)"}},"required":["dimension"],"allOf":[{"if":{"properties":{"provider":{"type":"string","minLength":1}},"required":["provider"]},"then":{"required":["provider","embedding_model"],"properties":{"dimension":{"type":"integer","minimum":2}}},"else":{"not":{"required":["embedding_model"]},"properties":{"dimension":{"const":1}}}}],"additionalProperties":false}}}},{"if":{"properties":{"name":{"const":"otel"}}},"then":{"required":["config"],"properties":{"config":{"anyOf":[{"$ref":"#/$defs/otel_profile_config"},{"$ref":"#/$defs/otel_profiles_config"}],"description":"Configuration for the OpenTelemetry plugin. Supports the legacy single-profile shape or the profiles wrapper for multiple collectors."}}}},{"if":{"properties":{"name":{"const":"datadog"}}},"then":{"required":["config"],"properties":{"config":{"type":"object","description":"Configuration for the Datadog APM and metrics plugin","properties":{"service_name":{"type":"string","description":"Name of the service to report to Datadog (can use env. prefix)","default":"bifrost"},"ml_app":{"type":"string","description":"ML application name for LLM Observability grouping (defaults to service_name, can use env. prefix)"},"agent_addr":{"type":"string","description":"Combined host:port address of the Datadog Agent for APM traces (agent mode only, can use env. prefix). Overridden by agent_host when set.","default":"localhost:8126"},"agent_host":{"type":"string","description":"Host of the Datadog Agent for APM traces, provided separately from the port (agent mode only, can use env. prefix). Takes precedence over agent_addr when set. Useful in Kubernetes where the host is injected via the downward API (status.hostIP)."},"agent_port":{"type":"string","description":"Port of the Datadog Agent for APM traces (agent mode only, can use env. prefix). Used only when agent_host is set (defaults to 8126); has no effect when agent_host is unset, in which case agent_addr supplies the port.","default":"8126"},"dogstatsd_addr":{"type":"string","description":"Combined host:port address of the DogStatsD server for metrics (agent mode only, can use env. prefix). Overridden by dogstatsd_host when set.","default":"localhost:8125"},"dogstatsd_host":{"type":"string","description":"Host of the DogStatsD server for metrics, provided separately from the port (agent mode only, can use env. prefix). Takes precedence over dogstatsd_addr when set. Useful in Kubernetes where the host is injected via the downward API (status.hostIP)."},"dogstatsd_port":{"type":"string","description":"Port of the DogStatsD server for metrics (agent mode only, can use env. prefix). Used only when dogstatsd_host is set (defaults to 8125); has no effect when dogstatsd_host is unset, in which case dogstatsd_addr supplies the port.","default":"8125"},"env":{"type":"string","description":"Environment tag (e.g., production, staging, can use env. prefix)"},"version":{"type":"string","description":"Service version tag (can use env. prefix)"},"custom_tags":{"type":"object","additionalProperties":{"type":"string"},"description":"Additional tags to add to all traces and metrics (values can use env. prefix)"},"enable_metrics":{"type":"boolean","description":"Enable metrics emission (default: true)","default":true},"enable_traces":{"type":"boolean","description":"Enable APM traces (default: true)","default":true},"enable_llm_obs":{"type":"boolean","description":"Enable LLM Observability (default: true)","default":true},"group_traces_by_session":{"type":"boolean","description":"Group requests sharing the same x-bf-session-id header into a single APM trace, with each request as a top-level sibling span. Agent mode only; an inbound W3C traceparent takes precedence (default: false)","default":false},"disable_content_logging":{"type":"boolean","description":"Disable logging of message content to Datadog (default: false)","default":false},"agentless":{"type":"boolean","description":"Use agentless mode to send data directly to Datadog APIs (default: false)","default":false},"api_key":{"type":"string","description":"Datadog API key, required for agentless mode (can use env. prefix)"},"site":{"type":"string","description":"Datadog site/region (e.g., datadoghq.com, datadoghq.eu)","default":"datadoghq.com"},"request_headers":{"type":"array","items":{"type":"string"},"description":"Request header names to capture as span tags"},"plugin_span_filter":{"$ref":"#/$defs/plugin_span_filter"}},"allOf":[{"if":{"properties":{"agentless":{"const":true}},"required":["agentless"]},"then":{"required":["api_key"]}}],"additionalProperties":false}}}},{"if":{"properties":{"name":{"const":"splunk"}}},"then":{"required":["config"],"properties":{"config":{"type":"object","description":"Configuration for the Splunk HEC observability plugin (Enterprise)","properties":{"endpoint":{"type":"string","minLength":1,"description":"HEC base URL, e.g. https://localhost:8088. Do not include the /services/collector path (can use env. prefix)."},"token":{"type":"string","minLength":1,"description":"HEC token, sent as the 'Authorization: Splunk <token>' header (can use env. prefix)."},"events_index":{"type":"string","minLength":1,"description":"Splunk event index for per-request events. Required when enable_events is true."},"metrics_index":{"type":"string","minLength":1,"description":"Splunk metrics index for derived metrics. Required when enable_metrics is true."},"source":{"type":"string","description":"HEC source field on every event and metric","default":"bifrost"},"sourcetype":{"type":"string","description":"HEC sourcetype for events (metrics use bifrost:metric). Drives field extraction, so keep it stable.","default":"bifrost:llm"},"host":{"type":"string","description":"HEC host field (defaults to the server hostname)"},"enable_events":{"type":"boolean","description":"Emit one flattened event per request to the event index (default: true)","default":true},"enable_metrics":{"type":"boolean","description":"Derive and emit the metric set to the metrics index (default: true)","default":true},"disable_content_logging":{"type":"boolean","description":"Strip request/response content from events (default: false)","default":false},"ca_cert":{"type":"string","description":"PEM-encoded CA certificate to verify the HEC server's TLS certificate (can use env. prefix)"},"insecure_skip_verify":{"type":"boolean","description":"Disable TLS certificate verification. Development or isolated environments only; use ca_cert in production (default: false)","default":false},"custom_fields":{"type":"object","additionalProperties":{"type":"string"},"description":"Static key/value fields added to every event and metric"},"request_headers":{"type":"array","items":{"type":"string"},"description":"Request-header name patterns to capture onto events. Use an explicit allowlist of exact header names; wildcard patterns can capture credentials such as Authorization."},"batch_max_bytes":{"type":"integer","minimum":1,"description":"Flush a HEC batch once its concatenated bodies reach this many bytes","default":524288},"flush_interval_ms":{"type":"integer","minimum":1,"description":"Maximum time buffered events wait before a flush","default":1000},"post_workers":{"type":"integer","minimum":1,"maximum":64,"description":"Concurrent HEC poster goroutines (capped at 64)","default":4},"indexer_ack":{"type":"boolean","description":"Enable HEC indexer acknowledgement for confirmed delivery. Set when the HEC token has acknowledgement enabled (default: false).","default":false},"ack_poll_interval_ms":{"type":"integer","minimum":1,"description":"How often to poll Splunk for acknowledgement, in ms (only used when indexer_ack is true)","default":1000},"ack_timeout_ms":{"type":"integer","minimum":1,"description":"How long an unacknowledged batch waits before the client resends it, in ms (only used when indexer_ack is true)","default":30000},"max_ack_attempts":{"type":"integer","minimum":1,"description":"Maximum POST attempts per batch before it is dropped (only used when indexer_ack is true)","default":3}},"required":["endpoint","token"],"allOf":[{"if":{"properties":{"enable_events":{"not":{"const":false}}}},"then":{"required":["events_index"]}},{"if":{"properties":{"enable_metrics":{"not":{"const":false}}}},"then":{"required":["metrics_index"]}}],"additionalProperties":false}}}},{"if":{"properties":{"name":{"const":"bigquery"}}},"then":{"required":["config"],"properties":{"config":{"type":"object","description":"Configuration for the BigQuery traces plugin","properties":{"project_id":{"type":"string","description":"GCP project ID (required)"},"dataset_id":{"type":"string","description":"BigQuery dataset name","default":"bifrost_traces"},"table_id":{"type":"string","description":"BigQuery table name","default":"traces"},"location":{"type":"string","description":"BigQuery dataset location","default":"US"},"service_account_key":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}],"description":"Service account key JSON for authentication. Omit to use Application Default Credentials (ADC). Supports env var syntax: \"env.MY_VAR\"."},"create_table_if_not_exists":{"type":"boolean","description":"Auto-create the table if it does not exist","default":true},"flush_interval_seconds":{"type":"integer","description":"Interval between buffer flushes, in seconds","default":5},"buffer_size":{"type":"integer","description":"Max rows to buffer before flushing","default":500},"custom_labels":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}]},"description":"Arbitrary key-value pairs stored as JSON in the labels column"},"disable_content_logging":{"type":"boolean","description":"Omit conversation history (input/output) columns","default":false},"request_headers":{"type":"array","items":{"type":"string"},"description":"Request-header name patterns (exact or wildcard like \"x-custom-*\") whose values are stored in the request_headers column"},"plugin_span_filter":{"$ref":"#/$defs/plugin_span_filter"}},"required":["project_id"],"additionalProperties":false}}}},{"if":{"properties":{"name":{"const":"kafka"}}},"then":{"required":["config"],"properties":{"config":{"type":"object","description":"Configuration for the Kafka traces plugin (Enterprise). Publishes completed traces as JSON messages to a Kafka topic, keyed by trace ID.","properties":{"brokers":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Kafka broker addresses (required)"},"topic":{"type":"string","description":"Kafka topic to publish traces to (required)"},"sasl_enabled":{"type":"boolean","description":"Enable SASL authentication. SASL is only activated when both sasl_enabled is true and sasl is set.","default":false},"sasl":{"type":"object","description":"SASL credentials, used when sasl_enabled is true","properties":{"mechanism":{"type":"string","enum":["PLAIN","SCRAM-SHA-256","SCRAM-SHA-512"]},"username":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}],"description":"SASL username. Supports env var syntax: \"env.MY_VAR\"."},"password":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}],"description":"SASL password. Supports env var syntax: \"env.MY_VAR\"."}},"additionalProperties":false},"tls_enabled":{"type":"boolean","description":"Enable TLS for broker connections","default":false},"ca_cert":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}],"description":"PEM-encoded CA certificate used to verify the broker's TLS certificate. Omit to use the system CA pool. Supports env var syntax: \"env.KAFKA_CA_CERT\"."},"compression":{"type":"string","enum":["none","gzip","snappy","lz4","zstd"],"description":"Compression codec for published messages","default":"none"},"batch_size":{"type":"integer","description":"Maximum number of messages batched per write","default":100},"flush_interval_ms":{"type":"integer","description":"Maximum time (ms) to wait before flushing a batch","default":1000},"auto_create_topic":{"type":"boolean","description":"Create the topic at startup if it does not exist","default":false},"disable_content_logging":{"type":"boolean","description":"Strip input/output message content from traces before publishing","default":false},"request_headers":{"type":"array","items":{"type":"string"},"description":"Request-header name patterns (exact or wildcard like \"x-custom-*\") whose captured values are embedded in published traces"},"plugin_span_filter":{"$ref":"#/$defs/plugin_span_filter"}},"required":["brokers","topic"],"additionalProperties":false}}}},{"if":{"properties":{"name":{"const":"pubsub"}}},"then":{"required":["config"],"properties":{"config":{"type":"object","description":"Configuration for the Google Cloud Pub/Sub traces plugin (Enterprise). Publishes completed traces as JSON messages to a Pub/Sub topic.","properties":{"project_id":{"type":"string","description":"GCP project ID (required)"},"topic_id":{"type":"string","description":"Pub/Sub topic ID — just the ID, not the full resource name (required)"},"service_account_key":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"additionalProperties":false}],"description":"Service account key JSON for authentication. Omit to use Application Default Credentials (ADC). Supports env var syntax: \"env.MY_VAR\"."},"auto_create_topic":{"type":"boolean","description":"Create the topic at startup if it does not exist","default":false},"disable_content_logging":{"type":"boolean","description":"Strip request/response content from traces before publishing","default":false},"request_headers":{"type":"array","items":{"type":"string"},"description":"Request-header name patterns (exact or wildcard like \"x-custom-*\") whose captured values are included in published traces"},"plugin_span_filter":{"$ref":"#/$defs/plugin_span_filter"}},"required":["project_id","topic_id"],"additionalProperties":false}}}}],"additionalProperties":false}},"websocket":{"$ref":"#/$defs/websocket_config"},"guardrails_config":{"$ref":"#/$defs/guardrails_config"},"audit_logs":{"$ref":"#/$defs/audit_logs_config"},"cluster_config":{"$ref":"#/$defs/cluster_config"},"load_balancer_config":{"$ref":"#/$defs/load_balancer_config"},"large_payload_optimization":{"$ref":"#/$defs/large_payload_optimization"},"scim_config":{"$ref":"#/$defs/scim_config"},"access_profiles":{"$ref":"#/$defs/access_profiles"},"feature_flags":{"$ref":"#/$defs/feature_flags_config"},"circuit_breaker_config":{"$ref":"#/$defs/circuit_breaker_config"}},"additionalProperties":false,"$defs":{"otel_endpoint":{"type":"string","anyOf":[{"format":"uri"},{"pattern":"^[^:\\s]+:\\d+$"},{"pattern":"^env\\.[A-Za-z_][A-Za-z0-9_]*$"}]},"plugin_span_filter":{"type":"object","description":"Controls which plugin hook spans this observability connector exports. Omit to export all plugin spans. Mode \"include\" exports only the listed plugins; mode \"exclude\" exports everything except them. Plugin names match the <name> in the span name \"plugin.<name>.<stage>\".","properties":{"mode":{"type":"string","enum":["include","exclude"]},"plugins":{"type":"array","items":{"type":"string"}}},"required":["mode","plugins"],"additionalProperties":false},"otel_profile_config":{"type":"object","description":"OpenTelemetry export profile. This legacy single-profile shape is still accepted directly as the plugin config.","properties":{"enabled":{"type":"boolean","description":"Whether this profile exports traces and metrics","default":true},"traces_enabled":{"type":"boolean","description":"Enable trace export for this profile. When false, no traces are sent and collector_url is not required, giving a metrics-only profile (pair with metrics_enabled).","default":true},"service_name":{"type":"string","description":"Service name to be used for tracing","default":"bifrost"},"collector_url":{"$ref":"#/$defs/otel_endpoint","description":"URL of the OpenTelemetry collector"},"trace_type":{"type":"string","description":"Type of trace to use for the OTEL collector","enum":["genai_extension","vercel","open_inference"]},"protocol":{"type":"string","description":"Protocol to use for the OTEL collector","enum":["http","grpc"]},"export_timeout":{"type":"integer","description":"Maximum time in seconds for a single trace export. Bounds how long a slow or unreachable collector can hold an export goroutine; gRPC exports have no other timeout.","default":5,"minimum":1,"maximum":60},"metrics_enabled":{"type":"boolean","description":"Enable push-based metrics export via OTLP. Recommended for multi-node cluster deployments.","default":false},"metrics_endpoint":{"$ref":"#/$defs/otel_endpoint","description":"OTLP metrics endpoint URL (e.g., http://otel-collector:4318/v1/metrics for HTTP or otel-collector:4317 for gRPC)"},"metrics_push_interval":{"type":"integer","description":"Metrics push interval in seconds","default":15,"minimum":1,"maximum":300},"request_headers":{"type":"array","items":{"type":"string"},"description":"Request header name patterns (exact or wildcard like x-custom-*) to capture and emit as span attributes. Note: * captures all headers including sensitive ones like Authorization."},"disable_content_logging":{"type":"boolean","description":"When true, message content (input/output messages, embeddings, tool definitions, and tool call arguments/results) is dropped from exported spans. Only metadata such as model, tokens, and latency is sent to the collector.","default":false},"group_traces_by_session":{"type":"boolean","description":"When true, requests sharing the same x-bf-session-id header are grouped into a single OTEL trace, each request's root span appearing as a top-level sibling. An inbound W3C traceparent takes precedence, leaving that request on its own distributed trace.","default":false},"disable_root_span_content":{"type":"boolean","description":"When true, input/output message content is dropped from the root span only; the underlying generation (llm.call) span retains the full content. This removes the duplicate input/output stored at the trace level (e.g. the Langfuse trace Input/Output goes empty) to reduce downstream storage.","default":false},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Custom headers sent to both the trace and metrics endpoints. Supports env.VAR_NAME prefix for environment variable substitution."},"trace_headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Extra headers sent only to the trace endpoint, overlaid on headers (same key wins). Supports env.VAR_NAME prefix."},"metrics_headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Extra headers sent only to the metrics endpoint, overlaid on headers (same key wins). Useful when a collector requires a per-signal header, e.g. a Databricks table name. Supports env.VAR_NAME prefix."},"tls_ca_cert":{"type":"string","description":"Path to TLS CA certificate file"},"insecure":{"type":"boolean","description":"Skip TLS verification (ignored if tls_ca_cert is set)","default":true},"plugin_span_filter":{"$ref":"#/$defs/plugin_span_filter"}},"allOf":[{"if":{"allOf":[{"not":{"properties":{"enabled":{"const":false}},"required":["enabled"]}},{"not":{"properties":{"traces_enabled":{"const":false}},"required":["traces_enabled"]}}]},"then":{"required":["collector_url","trace_type","protocol"]}},{"if":{"properties":{"metrics_enabled":{"const":true}},"required":["metrics_enabled"]},"then":{"required":["metrics_endpoint","protocol"]}}],"additionalProperties":false},"otel_profiles_config":{"type":"object","description":"OpenTelemetry plugin configuration with one or more export profiles.","properties":{"profiles":{"type":"array","description":"OpenTelemetry export profiles","items":{"$ref":"#/$defs/otel_profile_config"},"minItems":1},"plugin_span_filter":{"$ref":"#/$defs/plugin_span_filter"}},"required":["profiles"],"additionalProperties":false},"feature_flags_config":{"type":"object","description":"Boot-time overrides for feature flags. Flags themselves are declared in code via featureflags.Register; this block only sets initial values. Anything set here is rendered as locked in the UI - operators must edit the config (or Helm values) to change it.","properties":{"flags":{"type":"object","additionalProperties":{"type":"object","properties":{"enabled":{"description":"Boolean value or a string: either 'env.NAME' for env-var indirection, or a literal boolean (true / false / 1 / 0 / yes / no / on / off, case-insensitive).","oneOf":[{"type":"boolean"},{"type":"string","pattern":"^(env\\.[A-Za-z_][A-Za-z0-9_]*|[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|[01]|[Yy][Ee][Ss]|[Nn][Oo]|[Oo][Nn]|[Oo][Ff][Ff])$"}]}},"required":["enabled"],"additionalProperties":false}}},"additionalProperties":false},"routing_target":{"type":"object","description":"A single weighted routing target within a rule. All fields except weight are optional; omitting provider or model means use the incoming request value. Weights across all targets in a rule must sum to 1.","properties":{"provider":{"type":"string","minLength":1,"description":"Target provider name (e.g., 'openai', 'azure'). Omit to use the incoming request provider"},"model":{"type":"string","minLength":1,"description":"Target model name. Omit to use the incoming request model"},"provider_key_name":{"type":"string","minLength":1,"description":"Optional provider key name. Resolved to internal key_id at config load time. Requires provider."},"weight":{"type":"number","description":"Probability weight for this target (must be > 0). All weights in a rule must sum to 1","exclusiveMinimum":0,"maximum":1}},"required":["weight"],"additionalProperties":false},"routing_rule":{"type":"object","description":"Routing rule for dynamic provider/model selection","properties":{"id":{"type":"string","description":"Unique routing rule ID"},"name":{"type":"string","description":"Human-readable rule name"},"description":{"type":"string","description":"Optional description of the rule"},"enabled":{"type":"boolean","description":"Whether the rule is enabled","default":true},"cel_expression":{"type":"string","description":"CEL (Common Expression Language) expression for rule evaluation"},"chain_rule":{"type":"boolean","default":false,"description":"If true, re-evaluates routing chain after this rule matches"},"targets":{"type":"array","minItems":1,"description":"Weighted routing targets. Weights must sum to 1. Omit provider or model to use the incoming request value.","items":{"$ref":"#/$defs/routing_target"}},"provider":false,"model":false,"fallbacks":{"type":"array","description":"Fallback provider chain in order","items":{"type":"string"}},"scope":{"type":"string","enum":["global","team","customer","virtual_key"],"description":"Rule scope level","default":"global"},"scope_id":{"type":"string","description":"Entity ID for non-global scopes (required for non-global scope)"},"priority":{"type":"integer","description":"Evaluation priority within scope (lower = earlier)","default":0},"query":{"description":"Visual rule builder state for the management UI (react-querybuilder). Omitted unless exported from the dashboard. Not evaluated at routing runtime (cel_expression is). When present, must include combinator and rules[] so arbitrary metadata cannot be mistaken for builder state.","anyOf":[{"type":"null"},{"type":"object","required":["combinator","rules"],"properties":{"combinator":{"type":"string","enum":["and","or"]},"rules":{"type":"array","items":{"type":"object"},"description":"Leaf rules (field, operator, value) and/or nested combinator+rules groups from the query builder."}},"additionalProperties":true}]}},"required":["id","name","targets"],"additionalProperties":false,"if":{"properties":{"scope":{"enum":["team","customer","virtual_key"]}},"required":["scope"]},"then":{"required":["scope_id"],"properties":{"scope_id":{"type":"string","minLength":1}}}},"virtual_key_provider_config":{"type":"object","description":"Provider configuration for a virtual key","properties":{"id":{"type":"integer","description":"Provider config ID"},"virtual_key_id":{"type":"string","description":"Associated virtual key ID"},"provider":{"type":"string","description":"Provider name"},"weight":{"type":["number","null"],"description":"Weight for load balancing (null opts out of weighted routing)","default":null},"allowed_models":{"type":"array","description":"Allowed models for this provider config. Use [\"*\"] to allow all models; empty array denies all (deny-by-default).","items":{"type":"string"}},"blacklisted_models":{"type":"array","description":"Models blocked for this provider config even if matched by allowed_models. Use [\"*\"] to block all; empty array blocks none.","items":{"type":"string"}},"rate_limit_id":{"type":"string","description":"Associated rate limit ID"},"key_ids":{"type":"array","description":"Key identifiers allowed for this provider config. Use [\"*\"] to allow all keys. Semantics depend on version: v2 (default) empty = deny all; v1 empty = allow all. Values are the key IDs.","items":{"type":"string"}}},"required":["provider"],"additionalProperties":false},"virtual_key_mcp_config":{"type":"object","description":"MCP configuration for a virtual key","properties":{"id":{"type":"integer","description":"MCP config ID"},"virtual_key_id":{"type":"string","description":"Associated virtual key ID"},"mcp_client_id":{"type":"integer","description":"Associated MCP client ID (database format)"},"mcp_client_name":{"type":"string","description":"MCP client name (config file format — resolved to mcp_client_id at startup)"},"tools_to_execute":{"type":"array","description":"Include-only list of tools this Virtual Key is permitted to execute from this MCP client. ['*'] means all tools allowed, [] means no tools allowed (deny-by-default).","items":{"type":"string"}}},"additionalProperties":false},"complexity_tier_boundaries":{"type":"object","description":"Score thresholds used to classify complexity_tier values","properties":{"simple_medium":{"type":"number","exclusiveMinimum":0,"exclusiveMaximum":1,"description":"Scores below this threshold are SIMPLE"},"medium_complex":{"type":"number","exclusiveMinimum":0,"exclusiveMaximum":1,"description":"Scores below this threshold and at or above simple_medium are MEDIUM"},"complex_reasoning":{"type":"number","exclusiveMinimum":0,"exclusiveMaximum":1,"description":"Scores below this threshold and at or above medium_complex are COMPLEX"}},"required":["simple_medium","medium_complex","complex_reasoning"],"additionalProperties":false},"complexity_analyzer_keywords":{"type":"object","description":"User-editable keyword lists for complexity analysis","properties":{"code_keywords":{"type":"array","minItems":1,"items":{"type":"string","minLength":1}},"reasoning_keywords":{"type":"array","minItems":1,"items":{"type":"string","minLength":1}},"technical_keywords":{"type":"array","minItems":1,"items":{"type":"string","minLength":1}},"simple_keywords":{"type":"array","minItems":1,"items":{"type":"string","minLength":1}}},"required":["code_keywords","reasoning_keywords","technical_keywords","simple_keywords"],"additionalProperties":false},"complexity_analyzer_config":{"type":"object","description":"Runtime configuration for complexity_tier CEL routing","properties":{"tier_boundaries":{"$ref":"#/$defs/complexity_tier_boundaries"},"keywords":{"$ref":"#/$defs/complexity_analyzer_keywords"}},"required":["tier_boundaries","keywords"],"additionalProperties":false},"auth_config":{"type":"object","description":"Authentication configuration. Deprecated: Use governance.auth_config instead.","properties":{"admin_username":{"type":"string","description":"Admin username"},"admin_password":{"type":"string","description":"Admin password"},"is_enabled":{"type":"boolean","description":"Whether authentication is enabled"},"disable_auth_on_inference":{"type":"boolean","deprecated":true,"description":"Deprecated and ignored. Use client_config.enforce_auth_on_inference instead."}},"additionalProperties":false},"pricing_config":{"type":"object","properties":{"pricing_url":{"type":"string","description":"Pricing URL","optional":true,"format":"uri"},"pricing_sync_interval":{"type":"integer","description":"Pricing sync interval in seconds. Default is 24 hours. Minimum is 3600 seconds (1 hour).","default":86400,"optional":true,"minimum":3600},"model_parameters_url":{"type":"string","description":"Model parameters URL","optional":true,"format":"uri"},"mcp_library_url":{"type":"string","description":"URL to a custom MCP server catalog. Leave empty to use the default Bifrost catalog. Accepts a file:// URL to load the catalog from local disk in air-gapped deployments.","optional":true,"format":"uri"},"mcp_library_sync_interval":{"type":"integer","description":"MCP library sync interval in seconds. Set to 0 to disable background catalog syncing entirely (air-gapped deployments with no local catalog file); an explicit force-sync still works. Default is 24 hours; minimum when enabled is 3600 seconds (1 hour).","default":86400,"optional":true,"anyOf":[{"const":0},{"minimum":3600}]},"live_models_sync_interval":{"type":"integer","description":"How often each provider's list-models response is re-fetched in the background, in seconds. Without this, a model an upstream starts serving after the gateway booted stays invisible until a restart or a key edit. Set to 0 to disable background refresh. Default is 1 hour; minimum when enabled is 60 seconds.","default":3600,"optional":true,"anyOf":[{"const":0},{"minimum":60}]}},"additionalProperties":false},"network_config":{"type":"object","properties":{"base_url":{"type":"string","format":"uri","description":"Base URL for the provider (optional, required for Ollama)"},"extra_headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Additional headers to send with requests"},"default_request_timeout_in_seconds":{"type":"integer","minimum":1,"description":"Default request timeout in seconds"},"max_retries":{"type":"integer","minimum":0,"description":"Maximum number of retries"},"retry_backoff_initial":{"type":"integer","minimum":100,"description":"Initial retry backoff in milliseconds"},"retry_backoff_max":{"type":"integer","minimum":100,"description":"Maximum retry backoff in milliseconds"},"enforce_http2":{"type":"boolean","description":"Force HTTP/2 on provider connections (relevant for Bedrock and other net/http-based providers)"},"http2_ping_interval_in_seconds":{"type":"integer","minimum":0,"maximum":3600,"description":"Seconds of stream idle before a client-initiated HTTP/2 keepalive PING (0 = disabled; only when enforce_http2)."},"insecure_skip_verify":{"type":"boolean","description":"Disable TLS certificate verification for provider connections. This bypasses server certificate validation and should be used only as a last resort when a trusted CA chain cannot be configured. Prefer ca_cert_pem for self-signed or private CA deployments."},"ca_cert_pem":{"type":"string","description":"PEM-encoded CA certificate to trust for provider endpoint connections (e.g. self-signed or internal CA). Supports inline PEM or env.VAR_NAME."},"stream_idle_timeout_in_seconds":{"type":"integer","minimum":5,"maximum":3600,"description":"Idle timeout per stream chunk in seconds. If no data is received for this many seconds, the stream is closed. Default: 120."},"keep_alive_timeout_in_seconds":{"type":"integer","minimum":1,"maximum":3600,"description":"Idle keep-alive for pooled connections, in seconds. Set below the upstream server's keep-alive so Bifrost drops idle connections before the server closes them. Default: 30."},"max_conns_per_host":{"type":"integer","minimum":1,"maximum":10000,"description":"Maximum number of TCP connections per provider host. For HTTP/2 (e.g. Bedrock), each connection supports ~100 concurrent streams. Default: 5000."},"beta_header_overrides":{"type":"object","additionalProperties":{"type":"boolean"},"description":"Override default Anthropic beta header support per provider. Keys are header prefixes (e.g. 'redact-thinking-'), values are true (supported) or false (unsupported). Headers not listed use the built-in defaults."},"allow_private_network":{"type":"boolean","description":"Allow connections to RFC 1918 private IPs (10.x, 172.16.x, 192.168.x). Enable for providers on a k8s pod network, LAN, or private VPC. Loopback addresses (localhost, 127.0.0.1, ::1) remain allowed regardless of this setting. Link-local addresses (169.254.x.x) are always blocked.","default":false}},"additionalProperties":false},"network_config_without_base_url":{"type":"object","properties":{"extra_headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Additional headers to send with requests"},"default_request_timeout_in_seconds":{"type":"integer","minimum":1,"description":"Default request timeout in seconds"},"max_retries":{"type":"integer","minimum":0,"description":"Maximum number of retries"},"retry_backoff_initial":{"type":"integer","minimum":100,"description":"Initial retry backoff in milliseconds"},"retry_backoff_max":{"type":"integer","minimum":100,"description":"Maximum retry backoff in milliseconds"},"enforce_http2":{"type":"boolean","description":"Force HTTP/2 on provider connections (relevant for Bedrock and other net/http-based providers)"},"http2_ping_interval_in_seconds":{"type":"integer","minimum":0,"maximum":3600,"description":"Seconds of stream idle before a client-initiated HTTP/2 keepalive PING (0 = disabled; only when enforce_http2)."},"insecure_skip_verify":{"type":"boolean","description":"Disable TLS certificate verification for provider connections. This bypasses server certificate validation and should be used only as a last resort when a trusted CA chain cannot be configured. Prefer ca_cert_pem for self-signed or private CA deployments."},"ca_cert_pem":{"type":"string","description":"PEM-encoded CA certificate to trust for provider endpoint connections (e.g. self-signed or internal CA)"},"stream_idle_timeout_in_seconds":{"type":"integer","minimum":5,"maximum":3600,"description":"Idle timeout per stream chunk in seconds. If no data is received for this many seconds, the stream is closed. Default: 120."},"keep_alive_timeout_in_seconds":{"type":"integer","minimum":1,"maximum":3600,"description":"Idle keep-alive for pooled connections, in seconds. Set below the upstream server's keep-alive so Bifrost drops idle connections before the server closes them. Default: 30."},"max_conns_per_host":{"type":"integer","minimum":1,"maximum":10000,"description":"Maximum number of TCP connections per provider host. For HTTP/2 (e.g. Bedrock), each connection supports ~100 concurrent streams. Default: 5000."},"beta_header_overrides":{"type":"object","additionalProperties":{"type":"boolean"},"description":"Override default Anthropic beta header support per provider. Keys are header prefixes (e.g. 'redact-thinking-'), values are true (supported) or false (unsupported). Headers not listed use the built-in defaults."},"allow_private_network":{"type":"boolean","description":"Allow connections to RFC 1918 private IPs (10.x, 172.16.x, 192.168.x). Enable for providers on a k8s pod network, LAN, or private VPC. Loopback addresses (localhost, 127.0.0.1, ::1) remain allowed regardless of this setting. Link-local addresses (169.254.x.x) are always blocked.","default":false}},"additionalProperties":false},"openai_config":{"type":"object","description":"OpenAI-specific provider settings","properties":{"disable_store":{"type":"boolean","description":"Disable OpenAI Responses API conversation storage."}},"additionalProperties":false},"concurrency_and_buffer_size":{"type":"object","properties":{"concurrency":{"type":"integer","minimum":1,"description":"Number of concurrent requests"},"buffer_size":{"type":"integer","minimum":1,"description":"Buffer size for requests"}},"required":["concurrency","buffer_size"],"additionalProperties":false},"base_key":{"type":"object","properties":{"name":{"type":"string","description":"Name of the key"},"value":{"type":"string","description":"API key value (can use env. prefix)"},"models":{"type":"array","items":{"type":"string"},"description":"Models this key can access. Use [\"*\"] to allow all models; empty array denies all (deny-by-default)."},"weight":{"type":"number","minimum":0,"description":"Weight for load balancing"},"use_for_batch_api":{"type":"boolean","description":"Whether this key can be used for batch API operations (default: false)","default":false},"aliases":{"type":"object","additionalProperties":{"oneOf":[{"type":"string","minLength":1,"description":"Legacy shape: a bare provider-specific identifier. Equivalent to {\"model_id\": \"<value>\"}."},{"type":"object","properties":{"model_id":{"type":"string","minLength":1,"description":"Provider-specific identifier sent on the wire (deployment name, inference profile ID, fine-tuned model ID, etc.)."},"model_name":{"type":"string","description":"Canonical model name used for pricing, logging, and family inference."},"model_family":{"type":"string","enum":["anthropic","openai","mistral","cohere","gemini","nova","titan"],"description":"Underlying model family. Used by provider routing without substring-sniffing the wire model ID."},"description":{"type":"string"},"region":{"type":"string","description":"Per-alias region override (can use env. prefix)."},"api_version":{"type":"string","description":"Azure OpenAI api-version override for this alias."},"anthropic_version":{"type":"string","description":"Azure anthropic-version header override for Claude-on-Azure deployments."},"endpoint":{"type":"string","description":"Per-alias Azure endpoint override (can use env. prefix)."},"project_id":{"type":"string","description":"Per-alias project override shared across providers: Vertex uses it as the GCP project; Bedrock and Bedrock Mantle send it as the AWS project via the OpenAI-Project / anthropic-workspace-id header (can use env. prefix)."},"project_number":{"type":"string","description":"Per-alias Vertex project number override (can use env. prefix)."},"force_single_region":{"type":"boolean","description":"Per-alias Vertex override: when true, call the alias region as-is and skip promoting multi-region-only models to a multi-region endpoint."},"inference_profile_arn":{"type":"string","description":"Per-alias Bedrock inference profile ARN (can use env. prefix)."},"use_deployments_endpoint":{"type":"boolean","description":"Replicate: use the deployments endpoint instead of the predictions endpoint for this alias."},"use_anthropic_endpoints":{"type":"boolean","description":"Routes chat completions and responses requests through Anthropic-compatible endpoints."}},"required":["model_id"],"additionalProperties":false}]},"propertyNames":{"minLength":1},"description":"Model alias mappings: each entry maps a user-facing model name to either a bare provider identifier (legacy string shape) or an AliasConfig object carrying the wire identifier plus optional canonical name, family, and provider-specific overrides."}},"required":["name","weight"]},"bedrock_key":{"allOf":[{"$ref":"#/$defs/base_key"},{"type":"object","properties":{"bedrock_key_config":{"type":"object","properties":{"access_key":{"type":"string","description":"AWS access key (can use env. prefix)"},"secret_key":{"type":"string","description":"AWS secret key (can use env. prefix)"},"session_token":{"type":"string","description":"AWS session token (can use env. prefix)"},"region":{"type":"string","description":"AWS region"},"arn":{"type":"string","description":"AWS ARN"},"role_arn":{"type":"string","description":"AWS IAM role ARN for AssumeRole (can use env. prefix)"},"external_id":{"type":"string","description":"External ID for AssumeRole (can use env. prefix)"},"session_name":{"type":"string","description":"Role session name for AssumeRole (can use env. prefix)"},"batch_role_arn":{"type":"string","description":"Service role ARN passed to Bedrock batch jobs for S3 access; takes priority over any role_arn sent in the request (can use env. prefix)"},"project_id":{"type":"string","description":"Bedrock project ID scoping the Mantle sub-surface (OpenAI-compatible gpt-*/Gemma routing) via the OpenAI-Project header. When empty, AWS routes to the account's default project. No effect on the Converse/bedrock-runtime paths (can use env. prefix)."},"batch_s3_config":{"type":"object","description":"S3 bucket configuration for Bedrock batch operations","properties":{"buckets":{"type":"array","description":"List of S3 bucket configurations","items":{"type":"object","properties":{"bucket_name":{"type":"string","description":"S3 bucket name"},"prefix":{"type":"string","description":"S3 key prefix for batch files"},"is_default":{"type":"boolean","description":"Whether this is the default bucket for batch operations"}},"required":["bucket_name"],"additionalProperties":false}}},"additionalProperties":false},"endpoints":{"type":"object","description":"Interface VPC endpoint (AWS PrivateLink) hosts to dial instead of the public regional endpoints. Use the endpoint's DNS name from the VPC console, not the endpoint ID — AWS appends a random string that cannot be derived. Region is still required: it sets the SigV4 credential scope.","properties":{"runtime":{"type":"string","pattern":"\\.","description":"Host for the bedrock-runtime service, which serves all inference (can use env. prefix)"},"control_plane":{"type":"string","pattern":"\\.","description":"Host for the bedrock service, which serves model listing and batch jobs (can use env. prefix)"},"mantle":{"type":"string","pattern":"\\.","description":"Host for the bedrock-mantle service, which serves mantle-routed models (can use env. prefix)"},"agent_runtime":{"type":"string","pattern":"\\.","description":"Host for the bedrock-agent-runtime service, which serves rerank (can use env. prefix)"},"s3":{"type":"string","pattern":"\\.","description":"Host for the s3 service, which serves batch file I/O. Must carry the literal \"bucket.\" prefix, since Bifrost prepends the bucket name to build the virtual-hosted URL. A Gateway endpoint needs no value here (can use env. prefix)"}},"additionalProperties":false}},"required":["region"],"additionalProperties":false}}}]},"bedrock_mantle_key":{"allOf":[{"$ref":"#/$defs/base_key"},{"type":"object","properties":{"bedrock_mantle_key_config":{"type":"object","properties":{"access_key":{"type":"string","description":"AWS access key for SigV4 (can use env. prefix)"},"secret_key":{"type":"string","description":"AWS secret key for SigV4 (can use env. prefix)"},"session_token":{"type":"string","description":"AWS session token for temporary credentials (can use env. prefix)"},"region":{"type":"string","description":"AWS region used to build the bedrock-mantle endpoint host"},"role_arn":{"type":"string","description":"AWS IAM role ARN for AssumeRole (can use env. prefix)"},"external_id":{"type":"string","description":"External ID for AssumeRole (can use env. prefix)"},"session_name":{"type":"string","description":"Role session name for AssumeRole (can use env. prefix)"},"project_id":{"type":"string","description":"Bedrock project ID scoping inference and model listing. Sent as the OpenAI-Project header on the OpenAI-compatible surface and the anthropic-workspace-id header on the native-Anthropic (Claude) surface. When empty, AWS routes to the account's default project (can use env. prefix)."},"endpoints":{"type":"object","description":"Interface VPC endpoint (AWS PrivateLink) hosts to dial instead of the public regional endpoints. Use the endpoint's DNS name from the VPC console, not the endpoint ID — AWS appends a random string that cannot be derived. Region is still required: it sets the SigV4 credential scope.","properties":{"mantle":{"type":"string","pattern":"\\.","description":"Host for the bedrock-mantle service (can use env. prefix)"}},"additionalProperties":false}},"required":["region"],"additionalProperties":false}}}]},"vllm_key":{"allOf":[{"$ref":"#/$defs/base_key"},{"type":"object","properties":{"vllm_key_config":{"type":"object","properties":{"url":{"type":"string","minLength":1,"description":"VLLM server base URL (can use env. prefix)"},"model_name":{"type":"string","minLength":1,"description":"Exact model name served on this VLLM instance"}},"required":["url","model_name"],"additionalProperties":false},"use_anthropic_endpoints":{"type":"boolean","description":"Routes chat completions and responses requests through Anthropic-compatible endpoints.","default":false}},"required":["vllm_key_config"]}]},"replicate_key":{"allOf":[{"$ref":"#/$defs/base_key"},{"type":"object","properties":{"replicate_key_config":{"type":"object","properties":{"use_deployments_endpoint":{"type":"boolean","description":"Whether to use the deployments endpoint instead of the models endpoint (default: false)"}},"additionalProperties":false}}}]},"ollama_key":{"allOf":[{"$ref":"#/$defs/base_key"},{"type":"object","properties":{"ollama_key_config":{"type":"object","properties":{"url":{"type":"string","minLength":1,"description":"Ollama server base URL (can use env. prefix)"}},"required":["url"],"additionalProperties":false}},"required":["ollama_key_config"]}]},"sgl_key":{"allOf":[{"$ref":"#/$defs/base_key"},{"type":"object","properties":{"sgl_key_config":{"type":"object","properties":{"url":{"type":"string","minLength":1,"description":"SGLang server base URL (can use env. prefix)"}},"required":["url"],"additionalProperties":false},"use_anthropic_endpoints":{"type":"boolean","description":"Routes chat completions and responses requests through Anthropic-compatible endpoints.","default":false}},"required":["sgl_key_config"]}]},"deepseek_key":{"allOf":[{"$ref":"#/$defs/base_key"},{"type":"object","properties":{"use_anthropic_endpoints":{"type":"boolean","description":"Routes chat completions and responses requests through Anthropic-compatible endpoints.","default":false}}}]},"fireworks_key":{"allOf":[{"$ref":"#/$defs/base_key"},{"type":"object","properties":{"use_anthropic_endpoints":{"type":"boolean","description":"Routes chat completions and responses requests through Anthropic-compatible endpoints.","default":false}}}]},"azure_key":{"allOf":[{"$ref":"#/$defs/base_key"},{"type":"object","properties":{"azure_key_config":{"type":"object","properties":{"endpoint":{"type":"string","description":"Azure endpoint (can use env. prefix)"},"client_id":{"type":"string","description":"Azure client ID for Entra authentication (can use env. prefix)"},"client_secret":{"type":"string","description":"Azure client secret for Entra authentication (can use env. prefix)"},"tenant_id":{"type":"string","description":"Azure tenant ID for Entra authentication (can use env. prefix)"},"scopes":{"type":"array","items":{"type":"string"},"description":"OAuth2 scopes for Entra authentication"},"api_version":{"type":"string","description":"Deprecated: Bifrost now uses the Azure OpenAI v1 API which does not require an api-version parameter."}},"required":["endpoint"],"dependentRequired":{"client_id":["client_secret","tenant_id"],"client_secret":["client_id","tenant_id"],"tenant_id":["client_id","client_secret"]},"additionalProperties":false}},"required":["azure_key_config"]}]},"vertex_key":{"allOf":[{"$ref":"#/$defs/base_key"},{"type":"object","properties":{"vertex_key_config":{"type":"object","properties":{"project_id":{"type":"string","description":"Google Cloud project ID (can use env. prefix)"},"project_number":{"type":"string","description":"Google Cloud project number"},"region":{"type":"string","description":"Google Cloud region"},"auth_credentials":{"type":"string","description":"Authentication credentials (can use env. prefix)"},"force_single_region":{"type":"boolean","description":"When true, always call the configured region and skip automatic promotion of multi-region-only models to a multi-region endpoint. Enable for provisioned throughput.","default":false}},"required":["project_id","region"],"additionalProperties":false}},"required":["vertex_key_config"]}]},"provider":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/$defs/base_key"},"minItems":1,"description":"API keys for this provider"},"openai_config":{"$ref":"#/$defs/openai_config"},"network_config":{"$ref":"#/$defs/network_config"},"concurrency_and_buffer_size":{"$ref":"#/$defs/concurrency_and_buffer_size"},"proxy_config":{"$ref":"#/$defs/proxy_config"},"send_back_raw_request":{"type":"boolean","description":"Include raw request in BifrostResponse (default: false)"},"send_back_raw_response":{"type":"boolean","description":"Include raw response in BifrostResponse (default: false)"},"store_raw_request_response":{"type":"boolean","description":"Capture raw request/response for internal logging only; strip from API responses returned to clients (default: false)"},"custom_provider_config":{"$ref":"#/$defs/custom_provider_config"}},"required":["keys"],"additionalProperties":false},"provider_with_bedrock_config":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/$defs/bedrock_key"},"minItems":1,"description":"API keys for this provider"},"network_config":{"$ref":"#/$defs/network_config"},"concurrency_and_buffer_size":{"$ref":"#/$defs/concurrency_and_buffer_size"},"proxy_config":{"$ref":"#/$defs/proxy_config"},"send_back_raw_request":{"type":"boolean","description":"Include raw request in BifrostResponse (default: false)"},"send_back_raw_response":{"type":"boolean","description":"Include raw response in BifrostResponse (default: false)"},"store_raw_request_response":{"type":"boolean","description":"Capture raw request/response for internal logging only; strip from API responses returned to clients (default: false)"},"custom_provider_config":{"$ref":"#/$defs/custom_provider_config"}},"required":["keys"],"additionalProperties":false},"provider_with_bedrock_mantle_config":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/$defs/bedrock_mantle_key"},"minItems":1,"description":"API keys for this provider"},"network_config":{"$ref":"#/$defs/network_config"},"concurrency_and_buffer_size":{"$ref":"#/$defs/concurrency_and_buffer_size"},"proxy_config":{"$ref":"#/$defs/proxy_config"},"send_back_raw_request":{"type":"boolean","description":"Include raw request in BifrostResponse (default: false)"},"send_back_raw_response":{"type":"boolean","description":"Include raw response in BifrostResponse (default: false)"},"store_raw_request_response":{"type":"boolean","description":"Capture raw request/response for internal logging only; strip from API responses returned to clients (default: false)"},"custom_provider_config":{"$ref":"#/$defs/custom_provider_config"}},"required":["keys"],"additionalProperties":false},"provider_with_vllm_config":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/$defs/vllm_key"},"minItems":1,"description":"API keys for this provider"},"network_config":{"$ref":"#/$defs/network_config_without_base_url"},"concurrency_and_buffer_size":{"$ref":"#/$defs/concurrency_and_buffer_size"},"proxy_config":{"$ref":"#/$defs/proxy_config"},"send_back_raw_request":{"type":"boolean","description":"Include raw request in BifrostResponse (default: false)"},"send_back_raw_response":{"type":"boolean","description":"Include raw response in BifrostResponse (default: false)"},"store_raw_request_response":{"type":"boolean","description":"Capture raw request/response for internal logging only; strip from API responses returned to clients (default: false)"},"custom_provider_config":{"$ref":"#/$defs/custom_provider_config"}},"required":["keys"],"additionalProperties":false},"provider_with_replicate_config":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/$defs/replicate_key"},"minItems":1,"description":"API keys for this provider"},"network_config":{"$ref":"#/$defs/network_config"},"concurrency_and_buffer_size":{"$ref":"#/$defs/concurrency_and_buffer_size"},"proxy_config":{"$ref":"#/$defs/proxy_config"},"send_back_raw_request":{"type":"boolean","description":"Include raw request in BifrostResponse (default: false)"},"send_back_raw_response":{"type":"boolean","description":"Include raw response in BifrostResponse (default: false)"},"store_raw_request_response":{"type":"boolean","description":"Capture raw request/response for internal logging only; strip from API responses returned to clients (default: false)"},"custom_provider_config":{"$ref":"#/$defs/custom_provider_config"}},"required":["keys"],"additionalProperties":false},"provider_with_azure_config":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/$defs/azure_key"},"minItems":1,"description":"API keys for this provider"},"network_config":{"$ref":"#/$defs/network_config"},"concurrency_and_buffer_size":{"$ref":"#/$defs/concurrency_and_buffer_size"},"proxy_config":{"$ref":"#/$defs/proxy_config"},"send_back_raw_request":{"type":"boolean","description":"Include raw request in BifrostResponse (default: false)"},"send_back_raw_response":{"type":"boolean","description":"Include raw response in BifrostResponse (default: false)"},"store_raw_request_response":{"type":"boolean","description":"Capture raw request/response for internal logging only; strip from API responses returned to clients (default: false)"},"custom_provider_config":{"$ref":"#/$defs/custom_provider_config"}},"required":["keys"],"additionalProperties":false},"provider_with_vertex_config":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/$defs/vertex_key"},"minItems":1,"description":"API keys for this provider"},"network_config":{"$ref":"#/$defs/network_config"},"concurrency_and_buffer_size":{"$ref":"#/$defs/concurrency_and_buffer_size"},"proxy_config":{"$ref":"#/$defs/proxy_config"},"send_back_raw_request":{"type":"boolean","description":"Include raw request in BifrostResponse (default: false)"},"send_back_raw_response":{"type":"boolean","description":"Include raw response in BifrostResponse (default: false)"},"store_raw_request_response":{"type":"boolean","description":"Capture raw request/response for internal logging only; strip from API responses returned to clients (default: false)"},"custom_provider_config":{"$ref":"#/$defs/custom_provider_config"}},"required":["keys"],"additionalProperties":false},"provider_with_ollama_config":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/$defs/ollama_key"},"minItems":1,"description":"API keys for this provider"},"network_config":{"$ref":"#/$defs/network_config"},"concurrency_and_buffer_size":{"$ref":"#/$defs/concurrency_and_buffer_size"},"proxy_config":{"$ref":"#/$defs/proxy_config"},"send_back_raw_request":{"type":"boolean","description":"Include raw request in BifrostResponse (default: false)"},"send_back_raw_response":{"type":"boolean","description":"Include raw response in BifrostResponse (default: false)"},"store_raw_request_response":{"type":"boolean","description":"Capture raw request/response for internal logging only; strip from API responses returned to clients (default: false)"},"custom_provider_config":{"$ref":"#/$defs/custom_provider_config"}},"additionalProperties":false},"provider_with_sgl_config":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/$defs/sgl_key"},"minItems":1,"description":"API keys for this provider"},"network_config":{"$ref":"#/$defs/network_config"},"concurrency_and_buffer_size":{"$ref":"#/$defs/concurrency_and_buffer_size"},"proxy_config":{"$ref":"#/$defs/proxy_config"},"send_back_raw_request":{"type":"boolean","description":"Include raw request in BifrostResponse (default: false)"},"send_back_raw_response":{"type":"boolean","description":"Include raw response in BifrostResponse (default: false)"},"store_raw_request_response":{"type":"boolean","description":"Capture raw request/response for internal logging only; strip from API responses returned to clients (default: false)"},"custom_provider_config":{"$ref":"#/$defs/custom_provider_config"}},"additionalProperties":false},"provider_with_deepseek_config":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/$defs/deepseek_key"},"minItems":1,"description":"API keys for this provider"},"network_config":{"$ref":"#/$defs/network_config"},"concurrency_and_buffer_size":{"$ref":"#/$defs/concurrency_and_buffer_size"},"proxy_config":{"$ref":"#/$defs/proxy_config"},"send_back_raw_request":{"type":"boolean","description":"Include raw request in BifrostResponse (default: false)"},"send_back_raw_response":{"type":"boolean","description":"Include raw response in BifrostResponse (default: false)"},"store_raw_request_response":{"type":"boolean","description":"Capture raw request/response for internal logging only; strip from API responses returned to clients (default: false)"},"custom_provider_config":{"$ref":"#/$defs/custom_provider_config"}},"required":["keys"],"additionalProperties":false},"provider_with_fireworks_config":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/$defs/fireworks_key"},"minItems":1,"description":"API keys for this provider"},"network_config":{"$ref":"#/$defs/network_config"},"concurrency_and_buffer_size":{"$ref":"#/$defs/concurrency_and_buffer_size"},"proxy_config":{"$ref":"#/$defs/proxy_config"},"send_back_raw_request":{"type":"boolean","description":"Include raw request in BifrostResponse (default: false)"},"send_back_raw_response":{"type":"boolean","description":"Include raw response in BifrostResponse (default: false)"},"store_raw_request_response":{"type":"boolean","description":"Capture raw request/response for internal logging only; strip from API responses returned to clients (default: false)"},"custom_provider_config":{"$ref":"#/$defs/custom_provider_config"}},"required":["keys"],"additionalProperties":false},"mcp_client_config":{"type":"object","properties":{"client_id":{"type":"string","description":"MCP client identifier"},"name":{"type":"string","description":"Name of the MCP client"},"is_code_mode_client":{"type":"boolean","description":"Whether this client is a code mode client"},"connection_type":{"type":"string","enum":["stdio","http","sse","inprocess"],"description":"Connection type for MCP client"},"connection_string":{"type":"string","description":"HTTP or SSE URL (alternative to http_config sub-object, required for HTTP or SSE connections)"},"auth_type":{"type":"string","enum":["none","headers","oauth","per_user_oauth","per_user_headers","token_exchange"],"description":"Authentication type for MCP connection"},"token_exchange":{"type":"object","description":"Delegated token-exchange configuration for auth_type 'token_exchange': each caller's identity-provider token is exchanged at runtime for a short-lived token scoped to this server's audience. The exchange endpoint and grant shape come from the deployment's identity-provider integration; the application performing the exchange is configured here. Requires user-identity authentication to be available. The MCP client lands in `pending_verification` state at boot; an admin verifies it once from the UI (as themselves).","properties":{"audience":{"type":"string","minLength":1,"description":"Resource identifier the exchanged token is scoped to at the identity provider (e.g. 'api://jira-mcp')"},"use_idp_credentials":{"type":"boolean","description":"When true, performs the exchange as the SSO login application itself instead of client_id/client_secret below, which are then ignored. Some providers require this: Microsoft Entra ID's on-behalf-of grant only accepts an assertion audienced to the exchanging application, and Bifrost's SSO login flow always requests a token self-audienced to the SSO application, so a separate exchange application can never receive a usable one."},"client_id":{"type":"string","minLength":1,"description":"Identity-provider application authorized to perform exchanges for this audience — typically a dedicated registration carrying the token-exchange (or on-behalf-of) grant, separate from the SSO login application. Required unless use_idp_credentials is true. Can use env. or vault. prefix."},"client_secret":{"type":"string","description":"Secret for the exchange application; omit for public clients. Ignored when use_idp_credentials is true. Can use env. or vault. prefix."},"authorization_server_url":{"type":"string","description":"Overrides where the exchange request is sent, for identity providers that bind an audience to a specific authorization server distinct from the one used for SSO login (e.g. Okta's per-resource Custom Authorization Servers). Leave empty to use the deployment's SSO login issuer, which is correct for providers with one tenant-wide token endpoint (Entra, Auth0)."},"scopes":{"type":"array","items":{"type":"string"},"description":"Optional scopes to request on the exchanged token. Include 'offline_access' (where the identity provider supports it) so exchanges issue refresh tokens, keeping the retained admin discovery credential self-renewing."}},"required":["audience"],"allOf":[{"$comment":"client_id is required unless use_idp_credentials is true, in which case the exchange uses the SSO login application's own credentials instead.","if":{"not":{"properties":{"use_idp_credentials":{"const":true}},"required":["use_idp_credentials"]}},"then":{"required":["client_id"]}}],"additionalProperties":false},"oauth_config":{"type":"object","description":"Inline OAuth provider configuration for auth_type 'oauth' or 'per_user_oauth'. All inner fields are optional — missing URLs are resolved via RFC 8414 discovery and a missing client_id triggers RFC 7591 dynamic client registration at admin-click time. The client lands in `pending_verification` state at boot; an admin runs the verification flow once from the MCP Gateway UI.","properties":{"client_id":{"type":"string","description":"OAuth client ID (optional — Dynamic Client Registration is used when omitted; can use env. or vault. prefix)"},"client_secret":{"type":"string","description":"OAuth client secret (optional — omit for PKCE public clients or DCR; can use env. or vault. prefix)"},"authorize_url":{"type":"string","description":"Provider authorize endpoint (optional — discovered from the MCP server URL when omitted)"},"token_url":{"type":"string","description":"Provider token endpoint (optional — discovered when omitted)"},"registration_url":{"type":"string","description":"Dynamic Client Registration endpoint (optional — discovered when omitted)"},"scopes":{"type":"array","items":{"type":"string"},"description":"OAuth scopes to request"},"resource":{"type":"string","description":"OAuth resource indicator, RFC 8707 (optional: discovered from the MCP server's protected resource metadata when omitted)"}},"additionalProperties":false},"per_user_header_keys":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"description":"Required header names each end-user must supply when auth_type is 'per_user_headers'. Schema only — values are submitted per-user at runtime. The MCP client lands in `pending_verification` state at boot; an admin submits sample values once from the UI to verify the connection and discover tools."},"headers":{"type":"object","description":"Headers to send with the request (for headers auth type)","additionalProperties":{"type":"string"}},"stdio_config":{"type":"object","properties":{"command":{"type":"string","description":"Command to execute"},"args":{"type":"array","items":{"type":"string"},"description":"Command arguments"},"envs":{"type":"array","items":{"type":"string"},"description":"Environment variables"}},"required":["command"],"additionalProperties":false},"tools_to_execute":{"type":"array","items":{"type":"string"},"description":"Include-only list of tools to execute. ['*'] means all, [] means none."},"tools_to_auto_execute":{"type":"array","items":{"type":"string"},"description":"Auto-execute list of tools. ['*'] means all, [] means none."},"tool_sync_interval":{"description":"Per-client override for tool sync interval as a Go duration string in whole seconds (e.g. '10m', '90s'; '0s' or omitted = use global). Legacy integer nanoseconds are also accepted and must be a whole number of seconds.","oneOf":[{"type":"string","pattern":"^(?:0|\\d+h(?:\\d+m)?(?:\\d+s)?|\\d+m(?:\\d+s)?|\\d+s)$"},{"type":"integer","minimum":0,"multipleOf":1000000000}]},"tool_execution_timeout":{"description":"Per-client override for tool execution timeout. Accepts a Go duration string (e.g. '30s', '2m') or a bare integer treated as seconds. When set, overrides the global tool_manager_config.tool_execution_timeout for this MCP server only. Omit or set to 0 to use the global default.","oneOf":[{"type":"string","pattern":"^(?:\\d+(?:\\.\\d+)?(?:ns|us|µs|ms|s|m|h))+$"},{"type":"integer","minimum":0}]},"allowed_extra_headers":{"type":"array","items":{"type":"string"},"description":"Allowlist of request-level headers that callers may forward to this MCP server at execution time. Use ['*'] to allow all headers."},"is_ping_available":{"type":"boolean","description":"Whether the MCP server supports ping for health checks (default: true)","default":true},"needs_session_stickiness":{"type":"boolean","description":"Only meaningful for connection_type 'http': whether this client maintains one persistent connection reused across every caller (true) or connects fresh per call (false, the default for newly created clients). SSE and STDIO always behave as sticky regardless of this field.","default":false},"tool_pricing":{"type":"object","description":"Tool pricing map (tool name to cost per execution)","additionalProperties":{"type":"number","minimum":0}},"allow_on_all_virtual_keys":{"type":"boolean","description":"When true, this MCP server is accessible to all virtual keys without requiring explicit per-key assignment. All tools are allowed by default. If a virtual key has an explicit MCP config for this server, that config takes precedence and overrides this behaviour.","default":false},"disabled":{"type":"boolean","description":"When true, this MCP client is disabled (connection and workers are stopped while preserving configuration).","default":false},"tls_config":{"type":"object","description":"TLS configuration for HTTP and SSE connections. Not applicable to stdio or inprocess connection types.","properties":{"insecure_skip_verify":{"type":"boolean","description":"Disable TLS certificate verification. Takes priority over ca_cert_pem when both are set. Use only in development or trusted isolated environments. Not recommended for production."},"ca_cert_pem":{"type":"string","description":"PEM-encoded CA certificate to trust for MCP server connections. Use when the MCP server uses a self-signed or private CA certificate. Supports env.VAR_NAME syntax to read the certificate from an environment variable."}},"additionalProperties":false}},"required":["name","connection_type"],"additionalProperties":false,"allOf":[{"$comment":"per_user_headers requires the admin to declare which header names users must submit. OAuth-based types have no required input — the connection_string drives RFC 8414 discovery + RFC 7591 dynamic client registration when oauth_config is omitted or its inner fields are blank.","if":{"properties":{"auth_type":{"const":"per_user_headers"}},"required":["auth_type"]},"then":{"required":["per_user_header_keys"]}},{"$comment":"oauth_config only applies to OAuth-based auth types; forbid it elsewhere (including entries that omit auth_type, which defaults to headers) so a misplaced block fails validation instead of being silently ignored.","if":{"not":{"properties":{"auth_type":{"enum":["oauth","per_user_oauth"]}},"required":["auth_type"]}},"then":{"not":{"required":["oauth_config"]}}},{"$comment":"per_user_header_keys only applies to auth_type 'per_user_headers'; forbid it elsewhere so a misplaced declaration fails validation instead of being silently ignored.","if":{"not":{"properties":{"auth_type":{"const":"per_user_headers"}},"required":["auth_type"]}},"then":{"not":{"required":["per_user_header_keys"]}}},{"$comment":"token_exchange requires its scoping block (audience), and the block only applies to that auth type; forbid it elsewhere so a misplaced declaration fails validation instead of being silently ignored.","if":{"properties":{"auth_type":{"const":"token_exchange"}},"required":["auth_type"]},"then":{"required":["token_exchange"]},"else":{"not":{"required":["token_exchange"]}}}],"oneOf":[{"properties":{"connection_type":{"const":"stdio"}},"required":["stdio_config"]},{"properties":{"connection_type":{"const":"websocket"}},"required":["websocket_config"]},{"properties":{"connection_type":{"const":"http"}},"anyOf":[{"required":["http_config"]},{"required":["connection_string"]}]},{"properties":{"connection_type":{"const":"sse"}},"required":["connection_string"]}]},"mcp_tool_manager_config":{"type":"object","properties":{"tool_execution_timeout":{"type":["integer","string"],"description":"Tool execution timeout. Accepts a duration string (e.g. \"30s\", \"2m\") or an integer number of seconds (e.g. 30 means 30 seconds).","minimum":1,"default":30},"max_agent_depth":{"type":"integer","description":"Max agent depth","minimum":1,"default":10},"code_mode_binding_level":{"type":"string","enum":["server","tool"],"description":"How tools are exposed in VFS for code execution"},"disable_auto_tool_inject":{"type":"boolean","description":"When true, MCP tools are not automatically injected into requests. Tools are only included when explicitly specified via request context filters or headers, such as x-bf-mcp-include-tools or x-bf-mcp-include-clients.","default":false}}},"mcp_tool_group_config":{"type":"object","properties":{"id":{"type":"integer","minimum":1,"description":"DB ID of this tool group. When set, the reconciler updates the existing group with this ID rather than matching by name."},"name":{"type":"string","description":"Tool group name"},"description":{"type":"string","description":"Tool group description"},"enabled":{"type":"boolean","description":"Whether tool group is enabled","default":true},"tools":{"type":"array","minItems":1,"description":"MCP tools included in this group","items":{"type":"object","properties":{"mcp_client_id":{"type":"string","description":"MCP client ID"},"mcp_client_name":{"type":"string","description":"MCP client name (resolved to client_id at startup)"},"tool_names":{"type":"array","description":"Tool names to allow. Empty means all tools from this MCP client.","items":{"type":"string"}}},"oneOf":[{"required":["mcp_client_id"]},{"required":["mcp_client_name"]}],"additionalProperties":false}},"virtual_key_ids":{"type":"array","items":{"type":"string"}},"team_ids":{"type":"array","items":{"type":"string"}},"customer_ids":{"type":"array","items":{"type":"string"}},"user_ids":{"type":"array","items":{"type":"string"}},"provider_names":{"type":"array","items":{"type":"string"}},"api_key_ids":{"type":"array","items":{"type":"integer"}}},"required":["name","tools"],"additionalProperties":false},"weaviate_config":{"type":"object","description":"Weaviate configuration for vector store","properties":{"scheme":{"type":"string","description":"Weaviate server scheme (http or https) - REQUIRED"},"host":{"type":"string","description":"Weaviate server host (host:port) - REQUIRED"},"api_key":{"type":"string","description":"API key for Weaviate authentication (optional)"},"grpc_config":{"type":"object","properties":{"host":{"type":"string","description":"Weaviate server host (host:port). If host is without a port number then the 80 port for insecured and 443 port for secured connections will be used."},"secured":{"type":"boolean","description":"Secured set it to true if it's a secured connection"}}},"headers":{"type":"object","description":"Additional headers to send with requests"},"timeout":{"type":"string","pattern":"^[0-9]+(ns|us|µs|ms|s|m|h)$","description":"Timeout for Weaviate operations (e.g., '5s')"},"class_name":{"type":"string","description":"Class name for Weaviate vector store"},"properties":{"type":"array","items":{"type":"object"},"description":"Properties for Weaviate vector store"}},"required":["scheme","host"],"additionalProperties":false},"redis_config":{"type":"object","description":"Redis configuration for vector store (also used for Valkey-compatible endpoints)","properties":{"addr":{"type":"string","description":"Redis/Valkey server address (host:port) - REQUIRED (can use env. prefix)"},"username":{"type":"string","description":"Username for Redis AUTH (optional, can use env. prefix)"},"password":{"type":"string","description":"Password for Redis AUTH (optional, can use env. prefix)"},"db":{"type":"integer","description":"Redis database number (default: 0)","default":0},"use_tls":{"type":"boolean","description":"Use TLS for the Redis/Valkey connection (optional)","default":false},"insecure_skip_verify":{"type":"boolean","description":"Skip TLS certificate verification for Redis/Valkey connections. Use only when a trusted CA cannot be configured.","default":false},"ca_cert_pem":{"type":"string","description":"PEM-encoded CA certificate to trust for Redis/Valkey TLS connections"},"cluster_mode":{"type":"boolean","description":"Use Redis Cluster mode. Required for cluster configuration endpoints; when enabled, db must be 0.","default":false},"pool_size":{"type":"integer","description":"Maximum number of socket connections (optional)"},"max_active_conns":{"type":"integer","description":"Maximum number of active connections (optional)"},"min_idle_conns":{"type":"integer","description":"Minimum number of idle connections (optional)"},"max_idle_conns":{"type":"integer","description":"Maximum number of idle connections (optional)"},"conn_max_lifetime":{"type":"string","pattern":"^[0-9]+(ns|us|µs|ms|s|m|h)$","description":"Connection maximum lifetime (e.g., '30m')"},"conn_max_idle_time":{"type":"string","pattern":"^[0-9]+(ns|us|µs|ms|s|m|h)$","description":"Connection maximum idle time (e.g., '5m')"},"dial_timeout":{"type":"string","pattern":"^[0-9]+(ns|us|µs|ms|s|m|h)$","description":"Timeout for socket connection (e.g., '5s')"},"read_timeout":{"type":"string","pattern":"^[0-9]+(ns|us|µs|ms|s|m|h)$","description":"Timeout for socket reads (e.g., '3s')"},"write_timeout":{"type":"string","pattern":"^[0-9]+(ns|us|µs|ms|s|m|h)$","description":"Timeout for socket writes (e.g., '3s')"},"context_timeout":{"type":"string","pattern":"^[0-9]+(ns|us|µs|ms|s|m|h)$","description":"Timeout for Redis operations (e.g., '10s')"}},"required":["addr"],"additionalProperties":false},"qdrant_config":{"type":"object","description":"Qdrant configuration for vector store","properties":{"host":{"type":"string","description":"Qdrant server host - REQUIRED (can use env. prefix)"},"port":{"type":"integer","description":"Qdrant server port (default: 6334 for gRPC)","default":6334},"api_key":{"type":"string","description":"API key for authentication (optional, can use env. prefix)"},"use_tls":{"type":"boolean","description":"Use TLS for connection (optional)","default":false},"max_recv_msg_size_mb":{"type":"integer","description":"gRPC max receive message size in MB (default: 64). Increase when caching large payloads such as base64-encoded image generation responses.","minimum":1,"default":64}},"required":["host"],"additionalProperties":false},"pinecone_config":{"type":"object","description":"Pinecone configuration for vector store","properties":{"api_key":{"type":"string","description":"Pinecone API key - REQUIRED (can use env. prefix)"},"index_host":{"type":"string","description":"Index host URL from Pinecone console - REQUIRED (e.g., your-index.svc.environment.pinecone.io)"}},"required":["api_key","index_host"],"additionalProperties":false},"proxy_config":{"type":"object","description":"Proxy configuration for provider connections","properties":{"type":{"type":"string","enum":["none","http","socks5","environment"],"description":"Type of proxy to use"},"url":{"oneOf":[{"type":"string","pattern":"^env\\.[A-Za-z_][A-Za-z0-9_]*$"},{"type":"string","format":"uri"}],"description":"URL of the proxy server (supports env.VAR_NAME or a literal URI)"},"username":{"type":"string","description":"Username for proxy authentication (supports env.VAR_NAME)"},"password":{"type":"string","description":"Password for proxy authentication (supports env.VAR_NAME)"},"ca_cert_pem":{"type":"string","description":"PEM-encoded CA certificate to trust for TLS connections through the proxy (for SSL-intercepting proxies, supports env.VAR_NAME)"}},"required":["type"],"additionalProperties":false},"cluster_config":{"type":"object","description":"Cluster mode configuration","properties":{"enabled":{"type":"boolean","description":"Whether cluster mode is enabled"},"type":{"type":"string","enum":["mesh","broker"],"default":"mesh","description":"Clustering mode. 'mesh' (default) is peer-to-peer via memberlist gossip. 'broker' routes all cluster traffic through a central broker process, for platforms without peer-to-peer connectivity (e.g. Cloud Run)."},"region":{"type":"string","description":"Region label for cluster deployment (runtime default: unknown)"},"peers":{"type":"array","description":"List of peer addresses","items":{"type":"string","description":"Peer address in host:port format"}},"gossip":{"type":"object","description":"Gossip protocol configuration","properties":{"port":{"type":"integer","minimum":1,"maximum":65535,"description":"Port for gossip communication"},"config":{"type":"object","description":"Gossip protocol settings","properties":{"timeout_seconds":{"type":"integer","minimum":1,"description":"Timeout for operations in seconds"},"success_threshold":{"type":"integer","minimum":1,"description":"Number of successful probes required"},"failure_threshold":{"type":"integer","minimum":1,"description":"Number of failed probes before marking as failed"}},"required":["timeout_seconds","success_threshold","failure_threshold"],"additionalProperties":false}},"required":["port","config"],"additionalProperties":false},"grpc":{"type":"object","description":"gRPC transport settings for the cluster counter-sync layer. Replaces the memberlist gossip broadcast path for governance usage counters; memberlist continues to handle cluster membership and other entity sync.","properties":{"port":{"type":"integer","minimum":1,"maximum":65535,"description":"TCP port the gRPC server listens on","default":10102},"dial_timeout_seconds":{"type":"integer","minimum":1,"description":"How long to wait when dialing a peer, in seconds","default":5}},"additionalProperties":false},"discovery":{"type":"object","description":"Auto-discovery configuration for cluster nodes","properties":{"enabled":{"type":"boolean","description":"Whether auto-discovery is enabled"},"type":{"type":"string","enum":["kubernetes","dns","udp","consul","etcd","mdns"],"description":"Discovery mechanism type"},"service_name":{"type":"string","description":"Service name used for discovery (used by consul, etcd, UDP, and mDNS)"},"bind_port":{"type":"integer","minimum":1,"maximum":65535,"description":"Port to bind for cluster communication"},"dial_timeout":{"type":"string","description":"Timeout for discovery dial operations as a Go duration string (e.g. '5s', '1m')"},"allowed_address_space":{"type":"array","items":{"type":"string"},"description":"CIDR notation for allowed address spaces (e.g., ['10.0.0.0/8', '192.168.0.0/16'])"},"k8s_namespace":{"type":"string","description":"Kubernetes namespace for service discovery"},"k8s_label_selector":{"type":"string","description":"Kubernetes label selector for filtering pods"},"dns_names":{"type":"array","items":{"type":"string"},"description":"DNS names to resolve for node discovery. Supports env.VAR_NAME for environment variable substitution."},"udp_broadcast_port":{"type":"integer","minimum":1,"maximum":65535,"description":"Port for UDP broadcast discovery"},"consul_address":{"type":"string","description":"Consul server address for service discovery"},"etcd_endpoints":{"type":"array","items":{"type":"string"},"description":"Etcd endpoints for service discovery"},"mdns_service":{"type":"string","description":"mDNS service name for local network discovery"}},"required":["type"],"additionalProperties":false},"broker":{"type":"object","description":"Broker settings, used when type is 'broker'","properties":{"address":{"type":"string","description":"host:port of the broker that nodes dial"},"tls":{"type":"boolean","description":"Whether to dial the broker over TLS"},"auth_token":{"type":"string","description":"Optional shared secret sent on connect"},"listen_port":{"type":"integer","minimum":1,"maximum":65535,"description":"Port the broker process serves on (default: 50051)"}},"required":["address"],"additionalProperties":false}},"required":["enabled"],"additionalProperties":false},"scim_config":{"type":"object","description":"SAML/SCIM (System for Cross-domain Identity Management) configuration","properties":{"enabled":{"type":"boolean","description":"Whether SAML/SCIM authentication is enabled"},"provider":{"type":"string","enum":["okta","entra","keycloak","zitadel","google","sailpoint","generic"],"description":"SCIM provider type"},"config":{"type":"object","description":"Provider-specific configuration"}},"required":["enabled"],"additionalProperties":false,"allOf":[{"if":{"properties":{"enabled":{"const":true},"provider":{"const":"okta"}},"required":["enabled","provider"]},"then":{"properties":{"config":{"$ref":"#/$defs/okta_config"}}}},{"if":{"properties":{"enabled":{"const":true},"provider":{"const":"entra"}},"required":["enabled","provider"]},"then":{"properties":{"config":{"$ref":"#/$defs/entra_config"}}}},{"if":{"properties":{"enabled":{"const":true},"provider":{"const":"keycloak"}},"required":["enabled","provider"]},"then":{"properties":{"config":{"$ref":"#/$defs/keycloak_config"}}}},{"if":{"properties":{"enabled":{"const":true},"provider":{"const":"generic"}},"required":["enabled","provider"]},"then":{"properties":{"config":{"$ref":"#/$defs/generic_config"}}}},{"if":{"properties":{"enabled":{"const":true},"provider":{"const":"zitadel"}},"required":["enabled","provider"]},"then":{"properties":{"config":{"$ref":"#/$defs/zitadel_config"}}}},{"if":{"properties":{"enabled":{"const":true},"provider":{"const":"google"}},"required":["enabled","provider"]},"then":{"properties":{"config":{"$ref":"#/$defs/google_config"}}}},{"if":{"properties":{"enabled":{"const":true},"provider":{"const":"sailpoint"}},"required":["enabled","provider"]},"then":{"properties":{"config":{"$ref":"#/$defs/sailpoint_config"}}}}]},"okta_config":{"type":"object","description":"Okta JWT authentication configuration","properties":{"issuerUrl":{"type":"string","format":"uri","description":"Okta issuer URL (e.g., https://your-domain.okta.com/oauth2/default)"},"authServerType":{"type":"string","enum":["org","custom"],"description":"'org' for Org Authorization Server or 'custom' for Custom Authorization Server. Auto-detected from issuer URL when omitted."},"clientId":{"type":"string","description":"Okta application client ID"},"clientSecret":{"type":"string","description":"Okta client secret"},"apiToken":{"type":"string","description":"Okta API token for Admin API access (optional; enables 24-hour background user/group reconciliation)"},"audience":{"type":"string","description":"JWT audience for validation (optional)"},"additionalScopes":{"type":"array","items":{"type":"string"},"description":"Extra OAuth scopes requested on top of the base openid/profile/email/offline_access set. Primarily for Custom Authorization Servers where claims like 'groups' are gated behind a scope Bifrost does not request by default."},"userIdField":{"type":"string","description":"JWT claim field for user ID (default: 'sub')","default":"sub"},"teamIdsField":{"type":"string","description":"JWT claim field for team IDs (default: 'groups')","default":"groups"},"rolesField":{"type":"string","description":"JWT claim field for roles (default: 'roles')","default":"roles"},"attributeRoleMappings":{"$ref":"#/$defs/scim_attribute_role_mappings"},"roleResolutionStrategy":{"$ref":"#/$defs/scim_role_resolution_strategy"},"claimsSyncMode":{"$ref":"#/$defs/scim_claims_sync_mode"},"attributeTeamMappings":{"$ref":"#/$defs/scim_attribute_team_mappings"},"attributeBusinessUnitMappings":{"$ref":"#/$defs/scim_attribute_business_unit_mappings"},"authProxy":{"$ref":"#/$defs/scim_auth_proxy"},"claimScimAttributes":{"$ref":"#/$defs/scim_claim_attributes"},"provisioningToken":{"$ref":"#/$defs/scim_provisioning_token"}},"required":["issuerUrl","clientId","clientSecret"],"additionalProperties":false},"entra_config":{"type":"object","description":"Microsoft Entra ID (formerly Azure AD) JWT authentication configuration","properties":{"tenantId":{"type":"string","description":"Azure tenant ID or 'common' for multi-tenant applications"},"clientId":{"type":"string","description":"Application (client) ID from Azure portal"},"clientSecret":{"type":"string","description":"Client secret (optional, required for token revocation)"},"cloud":{"type":"string","enum":["commercial","gcc-high","dod"],"default":"commercial","description":"Cloud environment: 'commercial' (default), 'gcc-high' for US Government GCC High, or 'dod' for Department of Defense"},"audience":{"type":"string","description":"JWT audience for validation (default: clientId)"},"appIdUri":{"type":"string","format":"uri","description":"App ID URI for v1.0 tokens (e.g., api://{clientId})"},"userIdField":{"type":"string","description":"JWT claim field for user ID (default: 'oid')","default":"oid"},"teamIdsField":{"type":"string","description":"JWT claim field for team IDs (default: 'groups')","default":"groups"},"rolesField":{"type":"string","description":"JWT claim field for roles (default: 'roles')","default":"roles"},"attributeRoleMappings":{"$ref":"#/$defs/scim_attribute_role_mappings"},"roleResolutionStrategy":{"$ref":"#/$defs/scim_role_resolution_strategy"},"claimsSyncMode":{"$ref":"#/$defs/scim_claims_sync_mode"},"attributeTeamMappings":{"$ref":"#/$defs/scim_attribute_team_mappings"},"attributeBusinessUnitMappings":{"$ref":"#/$defs/scim_attribute_business_unit_mappings"},"authProxy":{"$ref":"#/$defs/scim_auth_proxy"},"claimScimAttributes":{"$ref":"#/$defs/scim_claim_attributes"},"provisioningToken":{"$ref":"#/$defs/scim_provisioning_token"}},"required":["tenantId","clientId"],"additionalProperties":false},"keycloak_config":{"type":"object","description":"Keycloak OIDC/SCIM authentication configuration","properties":{"serverUrl":{"type":"string","format":"uri","description":"Base URL of the Keycloak server (e.g., https://keycloak.company.com). Must NOT include the /realms/{realm} suffix."},"realm":{"type":"string","description":"Keycloak realm name (e.g., 'bifrost-prod')"},"clientId":{"type":"string","description":"Keycloak client ID for the confidential client used by Bifrost"},"clientSecret":{"type":"string","description":"Keycloak client secret. Supports env. prefix for environment variable references (e.g., 'env.KEYCLOAK_CLIENT_SECRET')"},"audience":{"type":"string","description":"Expected JWT audience (default: clientId)"},"userIdField":{"type":"string","description":"JWT claim field for user ID (default: 'sub')","default":"sub"},"teamIdsField":{"type":"string","description":"JWT claim field for team IDs (default: 'groups')","default":"groups"},"rolesField":{"type":"string","description":"JWT claim field for roles (default: 'roles')","default":"roles"},"attributeRoleMappings":{"$ref":"#/$defs/scim_attribute_role_mappings"},"roleResolutionStrategy":{"$ref":"#/$defs/scim_role_resolution_strategy"},"claimsSyncMode":{"$ref":"#/$defs/scim_claims_sync_mode"},"attributeTeamMappings":{"$ref":"#/$defs/scim_attribute_team_mappings"},"attributeBusinessUnitMappings":{"$ref":"#/$defs/scim_attribute_business_unit_mappings"},"authProxy":{"$ref":"#/$defs/scim_auth_proxy"}},"required":["serverUrl","realm","clientId","clientSecret"],"additionalProperties":false},"generic_config":{"type":"object","description":"Generic OIDC authentication configuration for any standards-compliant OIDC identity provider. Endpoints are resolved via OIDC discovery (.well-known/openid-configuration) from issuerUrl unless overridden.","properties":{"issuerUrl":{"type":"string","format":"uri","description":"OIDC issuer URL (e.g., https://idp.company.com)"},"clientId":{"type":"string","description":"OAuth2/OIDC application client ID"},"clientSecret":{"type":"string","description":"OAuth2/OIDC client secret (optional, for confidential clients). Supports env. prefix for environment variable references."},"audience":{"type":"string","description":"JWT audience for token validation (optional)"},"authorizationEndpoint":{"type":"string","format":"uri","description":"Manual override for the authorization endpoint (used in preference to discovery when set)"},"tokenEndpoint":{"type":"string","format":"uri","description":"Manual override for the token endpoint (used in preference to discovery when set)"},"userinfoEndpoint":{"type":"string","format":"uri","description":"Manual override for the userinfo endpoint (used in preference to discovery when set)"},"teamIdsField":{"type":"string","description":"JWT claim field for team/group IDs (default: 'groups')","default":"groups"},"rolesField":{"type":"string","description":"JWT claim field for role names (optional)"},"scopes":{"type":"array","description":"Override the default OIDC scopes requested during login","items":{"type":"string"}},"attributeRoleMappings":{"$ref":"#/$defs/scim_attribute_role_mappings"},"roleResolutionStrategy":{"$ref":"#/$defs/scim_role_resolution_strategy"},"claimsSyncMode":{"$ref":"#/$defs/scim_claims_sync_mode"},"attributeTeamMappings":{"$ref":"#/$defs/scim_attribute_team_mappings"},"attributeBusinessUnitMappings":{"$ref":"#/$defs/scim_attribute_business_unit_mappings"},"authProxy":{"$ref":"#/$defs/scim_auth_proxy"},"claimScimAttributes":{"$ref":"#/$defs/scim_claim_attributes"},"provisioningToken":{"$ref":"#/$defs/scim_provisioning_token"}},"required":["issuerUrl","clientId"],"additionalProperties":false},"zitadel_config":{"type":"object","description":"Zitadel OIDC authentication configuration","properties":{"domain":{"type":"string","format":"hostname","pattern":"^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$","description":"Zitadel instance domain (e.g., 'my-instance.zitadel.cloud' or 'auth.company.com'). Bare host only - do NOT include the scheme, port, or path."},"clientId":{"type":"string","description":"Zitadel application client ID"},"clientSecret":{"type":"string","description":"Zitadel client secret (optional, required for confidential clients and token revocation)"},"projectId":{"type":"string","description":"Optional Zitadel project ID for project-scoped role claims"},"audience":{"type":"string","description":"Access-token audience override (default: clientId)"},"serviceAccountClientId":{"type":"string","description":"Service account client ID for Zitadel Management API (required for user provisioning - web apps cannot use client_credentials)"},"serviceAccountClientSecret":{"type":"string","description":"Service account client secret for Zitadel Management API"},"teamIdsField":{"type":"string","description":"JWT claim field for team/group IDs (default: 'groups')","default":"groups"},"attributeRoleMappings":{"$ref":"#/$defs/scim_attribute_role_mappings"},"roleResolutionStrategy":{"$ref":"#/$defs/scim_role_resolution_strategy"},"claimsSyncMode":{"$ref":"#/$defs/scim_claims_sync_mode"},"attributeTeamMappings":{"$ref":"#/$defs/scim_attribute_team_mappings"},"attributeBusinessUnitMappings":{"$ref":"#/$defs/scim_attribute_business_unit_mappings"},"authProxy":{"$ref":"#/$defs/scim_auth_proxy"},"claimScimAttributes":{"$ref":"#/$defs/scim_claim_attributes"}},"required":["domain","clientId"],"additionalProperties":false},"google_config":{"type":"object","description":"Google Workspace OIDC authentication configuration","properties":{"domain":{"type":"string","format":"hostname","pattern":"^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$","description":"Google Workspace primary domain (e.g., 'company.com'). Bare host only - do NOT include the scheme, port, or path."},"clientId":{"type":"string","description":"Google OAuth2 client ID"},"clientSecret":{"type":"string","description":"Google client secret (optional, used for token revocation)"},"credentialMode":{"type":"string","enum":["","inherit","env","file"],"description":"How Directory API credentials are sourced. 'inherit' uses Application Default Credentials; 'env' uses serviceAccountEnvVar; 'file' uses serviceAccountFile."},"serviceAccountEnvVar":{"type":"string","description":"Name of the environment variable containing the service account JSON (used when credentialMode is 'env'). Required for Directory API access without ADC."},"serviceAccountFile":{"type":"string","description":"Filesystem path to the service account JSON key file (used when credentialMode is 'file')."},"adminEmail":{"type":"string","format":"email","description":"Admin email for domain-wide delegation. Required whenever Directory API access is configured."},"impersonateServiceAccount":{"type":"string","description":"GCP service account email to impersonate when using ADC (Workload Identity); must have domain-wide delegation configured."},"audience":{"type":"string","description":"JWT audience override (default: clientId)"},"teamIdsField":{"type":"string","description":"Claim field for team/group IDs (default: 'groups')","default":"groups"},"attributeRoleMappings":{"$ref":"#/$defs/scim_attribute_role_mappings"},"roleResolutionStrategy":{"$ref":"#/$defs/scim_role_resolution_strategy"},"claimsSyncMode":{"$ref":"#/$defs/scim_claims_sync_mode"},"attributeTeamMappings":{"$ref":"#/$defs/scim_attribute_team_mappings"},"attributeBusinessUnitMappings":{"$ref":"#/$defs/scim_attribute_business_unit_mappings"},"authProxy":{"$ref":"#/$defs/scim_auth_proxy"},"claimScimAttributes":{"$ref":"#/$defs/scim_claim_attributes"}},"required":["domain","clientId"],"additionalProperties":false,"allOf":[{"if":{"properties":{"credentialMode":{"const":"env"}},"required":["credentialMode"]},"then":{"required":["serviceAccountEnvVar"]}},{"if":{"properties":{"credentialMode":{"const":"file"}},"required":["credentialMode"]},"then":{"required":["serviceAccountFile"]}},{"if":{"properties":{"credentialMode":{"enum":["inherit","env","file"]}},"required":["credentialMode"]},"then":{"required":["adminEmail"]}}]},"sailpoint_config":{"type":"object","description":"SailPoint identity security platform configuration","properties":{"product":{"type":"string","enum":["isc","iiq"],"description":"SailPoint product type: 'isc' for Identity Security Cloud, 'iiq' for IdentityIQ"},"tenant":{"type":"string","description":"ISC tenant identifier (required for ISC product)"},"baseUrl":{"type":"string","format":"uri","description":"IdentityIQ base URL (required for IIQ product)"},"username":{"type":"string","description":"Username for IdentityIQ authentication"},"password":{"type":"string","description":"Password for IdentityIQ authentication"},"clientId":{"type":"string","description":"OAuth2 client ID"},"clientSecret":{"type":"string","description":"OAuth2 client secret"},"teamIdsField":{"type":"string","description":"JWT claim field for team IDs"},"attributeRoleMappings":{"$ref":"#/$defs/scim_attribute_role_mappings"},"roleResolutionStrategy":{"$ref":"#/$defs/scim_role_resolution_strategy"},"claimsSyncMode":{"$ref":"#/$defs/scim_claims_sync_mode"},"attributeTeamMappings":{"$ref":"#/$defs/scim_attribute_team_mappings"},"attributeBusinessUnitMappings":{"$ref":"#/$defs/scim_attribute_business_unit_mappings"},"authProxy":{"$ref":"#/$defs/scim_auth_proxy"},"claimScimAttributes":{"$ref":"#/$defs/scim_claim_attributes"},"provisioningToken":{"$ref":"#/$defs/scim_provisioning_token"}},"required":["product"],"additionalProperties":false},"scim_claim_attributes":{"type":"object","description":"Per-claim SCIM interpretation. Maps each claim name to its SCIM source type and attribute key.","additionalProperties":{"type":"object","properties":{"attributeType":{"type":"string","enum":["user","group"],"description":"'user' reads from SCIM User resource attributes; 'group' matches SCIM Group resource."},"attributeValue":{"type":"string","description":"SCIM attribute key or group match field for this claim."}},"required":["attributeType","attributeValue"],"additionalProperties":false}},"scim_provisioning_token":{"type":"string","description":"Bearer token that inbound SCIM 2.0 provisioning clients (the IdP's SCIM app) must present in the Authorization header to push users and groups to Bifrost. Normally minted in the dashboard (Governance -> User Provisioning) and shown once, but may be seeded here for declarative/GitOps deployments; supports the env. prefix for environment-variable references. Generate one with: openssl rand -base64 32 | tr '+/' '-_' | tr -d '='"},"scim_auth_proxy":{"type":"object","description":"Identity-aware proxy (IAP) fronting this SCIM/SSO provider — e.g. Cloudflare Access, a generic OIDC proxy, or AWS ALB (Zero Trust / ZTNA). When enabled, Bifrost takes the login credential from the proxy's header and validates it against the proxy's own keys (not the IdP's), then feeds the resulting claims into the active provider's mapping. Shared across every provider.","properties":{"enabled":{"type":"boolean","description":"Turn the auth-proxy layer on. When false the rest of this block is ignored."},"provider":{"type":"string","enum":["cloudflare-access","generic","aws-alb"],"default":"cloudflare-access","description":"Proxy type. 'cloudflare-access' and 'generic' validate OIDC RS256 tokens via JWKS; 'aws-alb' validates ES256 tokens from an AWS ALB via the regional per-kid public key."},"mode":{"type":"string","enum":["full","login_only"],"default":"login_only","description":"'login_only' (default) authenticates an already-provisioned user by identity (email/sub) without deriving permissions from the token. 'full' also runs attribute→role/team/business-unit mapping from the proxy token's claims — the IdP claims used for mapping must be passed through into the proxy-signed token."},"headerName":{"type":"string","description":"Request header carrying the proxy token. Defaults per provider (cloudflare-access: Cf-Access-Jwt-Assertion, aws-alb: x-amzn-oidc-data); required for the generic provider. Supports the env. prefix."},"issuerUrl":{"type":"string","description":"Proxy issuer URL, e.g. https://<team>.cloudflareaccess.com. Required for the cloudflare-access and generic providers; must be https. Supports the env. prefix."},"jwksUrl":{"type":"string","description":"Explicit JWKS URL for JWKS providers. Derived from issuerUrl for cloudflare-access; empty means OIDC discovery from the issuer (generic). Must be https. Supports the env. prefix."},"audience":{"type":"string","description":"Expected token audience (aud) for JWKS providers, binding the token to this application. Required unless allowedAudiences is set. Supports the env. prefix."},"allowedAudiences":{"type":"array","items":{"type":"string"},"description":"Alternative to a single audience: a list of accepted audiences. Must not contain blank entries."},"region":{"type":"string","description":"AWS ALB only: region for the public-key endpoint. Derived from the signer ARN when empty. Supports the env. prefix."},"expectedSigner":{"type":"string","description":"AWS ALB only: the ALB ARN to pin the token's signer to. Required for the aws-alb provider — the regional key signs tokens for every ALB in the region, so without this pin any ALB could mint an accepted token. Supports the env. prefix."},"publicKeyBaseUrl":{"type":"string","description":"AWS ALB only: override base URL for the public-key endpoint (keys fetched from <base>/<kid>). Needed for partitions like GovCloud whose endpoint differs from the standard region-derived URL; must be https. Supports the env. prefix."},"userIdClaim":{"type":"string","description":"Token claim used as the user identity. Default 'email'. Supports the env. prefix."}},"required":["enabled"],"additionalProperties":false,"allOf":[{"if":{"properties":{"enabled":{"const":true},"provider":{"const":"aws-alb"}},"required":["enabled","provider"]},"then":{"required":["expectedSigner"]}},{"if":{"properties":{"enabled":{"const":true},"provider":{"const":"generic"}},"required":["enabled","provider"]},"then":{"required":["issuerUrl","headerName"],"anyOf":[{"required":["audience"]},{"required":["allowedAudiences"]}]}},{"if":{"properties":{"enabled":{"const":true},"provider":{"const":"cloudflare-access"}},"required":["enabled","provider"]},"then":{"required":["issuerUrl"],"anyOf":[{"required":["audience"]},{"required":["allowedAudiences"]}]}},{"if":{"properties":{"enabled":{"const":true}},"required":["enabled"],"not":{"required":["provider"]}},"then":{"required":["issuerUrl"],"anyOf":[{"required":["audience"]},{"required":["allowedAudiences"]}]}}]},"load_balancer_config":{"type":"object","description":"Load balancer configuration for intelligent request routing","properties":{"enabled":{"type":"boolean","description":"Whether load balancing is enabled"},"direction_selection_enabled":{"type":"boolean","description":"Enable adaptive provider (direction) selection. Defaults to true; omit to leave on."},"route_selection_enabled":{"type":"boolean","description":"Enable adaptive per-key (route) selection. Defaults to true; omit to leave on."},"append_fallbacks_to_pinned":{"type":"boolean","description":"When a request already carries a provider, append the healthy providers eligible for its model as fallbacks behind any it configured. Defaults to false."},"reroute_failed_directions":{"type":"boolean","description":"When a pinned provider's direction is unhealthy, re-route the request to a healthy provider. Defaults to false."},"prune_failed_fallbacks":{"type":"boolean","description":"Drop unhealthy directions from a request's configured fallbacks. Defaults to false."},"tracker_config":{"type":"object","description":"Configuration for tracking route metrics and performance"},"bootstrap":{"type":"object","description":"Bootstrap data for initializing load balancer with historical metrics","properties":{"route_metrics":{"type":"object","description":"Historical metrics per route"},"direction_metrics":{"type":"object","description":"Historical metrics per direction"},"routes":{"type":"object","description":"Known routes"}}}},"required":["enabled"],"additionalProperties":false},"guardrails_config":{"type":"object","description":"Guardrails configuration for content moderation and policy enforcement","properties":{"guardrail_rules":{"type":"array","description":"List of guardrail rules","items":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the rule"},"name":{"type":"string","description":"Name of the guardrail rule"},"description":{"type":"string","description":"Description of what the rule does"},"enabled":{"type":"boolean","description":"Whether this rule is enabled"},"target":{"type":"string","enum":["llm","mcp"],"default":"llm","description":"Execution target for the rule. Omitted values default to LLM guardrails."},"cel_expression":{"type":"string","description":"CEL (Common Expression Language) expression for rule evaluation"},"query":{"description":"Visual rule builder state for the management UI (react-querybuilder). Omitted unless exported from the dashboard. Not evaluated at runtime (cel_expression is).","anyOf":[{"type":"null"},{"type":"object","required":["combinator","rules"],"properties":{"combinator":{"type":"string","enum":["and","or"]},"rules":{"type":"array","items":{"type":"object"},"description":"Leaf rules and/or nested combinator+rules groups from the query builder."}},"additionalProperties":true}]},"apply_to":{"type":"string","enum":["input","output","both"],"description":"When to apply the guardrail (input, output, or both)"},"sampling_rate":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of requests to apply this rule to (0-100)"},"timeout":{"type":"integer","minimum":0,"description":"Timeout in seconds for rule execution"},"max_turns_to_send":{"type":"integer","minimum":0,"description":"Number of historical conversation turns to send to the guardrail provider; the latest message is always included on top. 0 sends all turns."},"evaluation_mode":{"type":"string","enum":["bundled","per_turn"],"default":"bundled","description":"How the rule's turns are sent to its guardrail providers. 'bundled' (default) concatenates all turns into one guardrail call. 'per_turn' sends each turn in its own call, evaluated in isolation, to avoid context-sensitive classifiers (e.g. Bedrock prompt-attack) combining unrelated turns into a false-positive block; this scans every turn but uses more provider calls."},"stream_replay_event_interval_ms":{"type":"integer","minimum":0,"maximum":1000,"default":0,"description":"Delay in milliseconds between consecutive buffered events after block-capable streaming output guardrails allow the response. 0 sends the buffered events immediately."},"provider_config_ids":{"type":"array","items":{"type":"integer"},"description":"IDs of provider configurations to use with this rule"}},"required":["id","name","enabled","cel_expression","apply_to"],"additionalProperties":false}},"guardrail_providers":{"type":"array","description":"List of guardrail provider configurations","items":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the provider config"},"provider_name":{"type":"string","description":"Name of the guardrail provider (e.g., 'bedrock', 'azure')"},"policy_name":{"type":"string","description":"Name of the specific policy to use"},"enabled":{"type":"boolean","description":"Whether this provider config is enabled"},"timeout":{"type":"integer","minimum":0,"description":"Timeout in seconds for provider execution"},"config":{"type":"object","description":"Provider-specific configuration"}},"required":["id","provider_name","policy_name","enabled"],"additionalProperties":false}}},"additionalProperties":false},"access_profiles":{"type":"array","description":"Enterprise access profile templates for RBAC-driven governance controls","items":{"$ref":"#/$defs/access_profile"}},"budget_line":{"type":"object","allOf":[{"if":{"required":["reset_config"]},"then":{"required":["reset_duration"],"properties":{"reset_duration":{"const":"1Q"}}}}],"properties":{"id":{"type":"string","description":"Budget ID"},"max_limit":{"type":"number","description":"Maximum spend limit in USD"},"reset_duration":{"type":"string","description":"Reset window, e.g. \"1M\", \"1h\", \"1Q\""},"reset_config":{"type":"object","description":"Window settings the reset duration cannot express. Only valid when reset_duration is quarterly (\"1Q\").","properties":{"quarter_start_month":{"type":"integer","minimum":0,"maximum":12,"description":"First month of Q1 as 1-12, so a fiscal year opening in April (4) gives Apr-Jun / Jul-Sep / Oct-Dec / Jan-Mar. Omitted or 0 means January. Quarter boundaries repeat every three months, so only the value modulo 3 changes the reset dates."}},"additionalProperties":false},"calendar_aligned":{"type":"boolean","description":"Deprecated: set calendar_aligned on the parent access profile instead. Kept for backward compatibility with older config.json files; the access-profile reconciler promotes any true value here to the profile's top-level calendar_aligned at load time.","default":false}},"required":["id","max_limit","reset_duration"],"additionalProperties":false},"rate_limit_line":{"type":"object","properties":{"id":{"type":"string","description":"Rate limit ID"},"token_max_limit":{"type":"integer","description":"Maximum number of tokens allowed in the reset window"},"token_reset_duration":{"type":"string","description":"Token reset window, e.g. \"1M\", \"1h\""},"request_max_limit":{"type":"integer","description":"Maximum number of requests allowed in the reset window"},"request_reset_duration":{"type":"string","description":"Request reset window, e.g. \"1M\", \"1h\""},"calendar_aligned":{"type":"boolean","description":"Deprecated: set calendar_aligned on the parent access profile instead. Kept for backward compatibility with older config.json files; the access-profile reconciler promotes any true value here to the profile's top-level calendar_aligned at load time.","default":false}},"required":["id"],"additionalProperties":false},"access_profile":{"type":"object","properties":{"name":{"type":"string","description":"Unique access profile name"},"description":{"type":"string","description":"Optional profile description"},"is_active":{"type":"boolean","description":"Whether this access profile is active","default":true},"tags":{"type":"array","items":{"type":"string"},"description":"Optional list of tags for filtering and grouping profiles"},"budgets":{"type":"array","items":{"$ref":"#/$defs/budget_line"},"description":"Profile-level budgets"},"rate_limit":{"$ref":"#/$defs/rate_limit_line"},"calendar_aligned":{"type":"boolean","description":"Snap budget and rate-limit reset windows to clean calendar boundaries (day, week, month, year) for this profile","default":false},"provider_configs":{"type":"array","description":"Per-provider restrictions and limits for this profile","items":{"$ref":"#/$defs/access_profile_provider_config"}},"mcp_tool_groups":{"type":"array","description":"MCP tool group associations","items":{"$ref":"#/$defs/access_profile_mcp_tool_group"}},"mcp_servers":{"type":"array","description":"MCP server associations","items":{"$ref":"#/$defs/access_profile_mcp_server"}},"mcp_tool_overrides":{"type":"array","description":"Per-tool include/exclude MCP overrides","items":{"$ref":"#/$defs/access_profile_mcp_tool_override"}}},"required":["name"],"additionalProperties":false},"access_profile_provider_config":{"type":"object","properties":{"provider_name":{"type":"string","description":"Provider name"},"all_models_allowed":{"type":"boolean","description":"Whether all models under this provider are allowed","default":false},"allowed_models":{"type":"array","items":{"type":"string"},"description":"Allowed model names (ignored when all_models_allowed is true)"},"budgets":{"type":"array","items":{"$ref":"#/$defs/budget_line"}},"rate_limit":{"$ref":"#/$defs/rate_limit_line"},"blacklisted_models":{"type":"array","description":"Models blocked for this provider even if matched by allowed_models (denylist wins). Use [\"*\"] to block all; empty array or omitted blocks none.","items":{"type":"string"}},"weight":{"type":["number","null"],"description":"Load-balancer seed weight for this provider (null opts out of weighted routing / no override)","default":null},"model_budgets":{"type":"array","description":"Per-model budgets and rate limits scoped to this provider. Each entry targets one model and materializes as a user-scoped model config on assignment.","items":{"$ref":"#/$defs/access_profile_provider_model_budget"}},"key_ids":{"type":"array","description":"Key IDs allowed for this provider config. Use [\"*\"] to allow all keys; empty array or omitted denies all keys. Specific IDs restrict access to those keys only.","items":{"type":"string"}}},"required":["provider_name"],"additionalProperties":false},"access_profile_provider_model_budget":{"type":"object","description":"Per-model budget group under an access-profile provider config, with optional per-model rate limit","properties":{"model_name":{"type":"string","description":"Model this budget group applies to (scoped to the parent provider)"},"budgets":{"type":"array","description":"Spend budgets for this model","items":{"$ref":"#/$defs/budget_line"}},"rate_limit":{"$ref":"#/$defs/rate_limit_line"}},"required":["model_name"],"additionalProperties":false},"access_profile_mcp_tool_group":{"type":"object","properties":{"tool_group_id":{"type":"integer","minimum":1,"description":"MCP tool group ID"}},"required":["tool_group_id"],"additionalProperties":false},"access_profile_mcp_server":{"type":"object","properties":{"mcp_server_id":{"type":"string","description":"MCP server identifier"}},"required":["mcp_server_id"],"additionalProperties":false},"access_profile_mcp_tool_override":{"type":"object","properties":{"mcp_client_id":{"type":"string","description":"MCP client identifier"},"tool_name":{"type":"string","description":"Tool name"},"action":{"type":"string","enum":["include","exclude"],"description":"Override action"}},"required":["mcp_client_id","tool_name","action"],"additionalProperties":false},"audit_logs_config":{"type":"object","description":"Audit logs configuration for CADF-compliant activity logging","properties":{"disabled":{"type":"boolean","description":"Whether audit logging is disabled (default: false)","default":false},"hmac_key":{"type":"string","description":"HMAC secret key for signing audit events (minimum 32 bytes). Can use env. prefix for environment variables (e.g., 'env.AUDIT_HMAC_KEY')."},"retention_days":{"type":"integer","minimum":0,"description":"Number of days to retain audit logs (0 means no retention limit)"},"archive_interval":{"type":"string","pattern":"^[0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h)([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))*$","description":"Archival window size and job period, as a Go duration string (e.g. '24h', '1h', '30m'). Each window is archived as one set of objects. The supported range is 5m and above: this schema accepts any valid duration, and the runtime raises anything shorter to 5m rather than failing startup. Only used when object_storage is set. Default: 24h","default":"24h"},"archive_grace_period":{"type":"string","pattern":"^[0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h)([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))*$","description":"How long to hold a window open past its end before archiving it, as a Go duration string. Absorbs clock skew and late inserts. Must be shorter than archive_interval, otherwise no window would ever close and archival would silently do nothing; the runtime substitutes a safe default if it is not. Only used when object_storage is set. Default: 15m","default":"15m"},"archive_max_object_bytes":{"type":"integer","minimum":1048576,"maximum":4294967296,"description":"Roll a new part object once the current part reaches this many uncompressed bytes. Bounds memory on the archiving node: peak usage is roughly 2x this value when compress is true, since gzip allocates a second full buffer. The minimum and maximum above are strict validation limits — a config outside [1MiB, 4GiB] is rejected as invalid rather than accepted and adjusted. (The runtime additionally clamps to the same range, as a safety net for configs that never pass through schema validation.) Only used when object_storage is set. Default: 134217728 (128MiB)","default":134217728},"object_storage":{"type":"object","description":"Optional object storage for archiving audit events to S3/GCS. Setting this enables archival; there is no separate on/off flag. A background job periodically copies audit events from the database, one closed time window at a time, writing each window as one or more JSONL part objects plus a manifest listing them: {prefix}/audit-logs/YYYY/MM/DD/{start}-{end}/part-00000.jsonl[.gz] and .../manifest.json[.gz]. Read the manifest to enumerate a window and ignore any object it does not list. The database remains the source of truth and archival never deletes rows (retention_days handles that independently). In a cluster only one node archives a given window. Archival is going-forward only; existing rows are not backfilled. See archive_interval, archive_grace_period and archive_max_object_bytes to tune it.","properties":{"type":{"type":"string","enum":["s3","gcs"],"description":"Object storage backend type"},"bucket":{"anyOf":[{"type":"string","minLength":1},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"Bucket name. Supports env var reference (e.g. env.S3_BUCKET) or a structured secret reference."},"prefix":{"type":"string","description":"Configurable base key path for stored audit objects; the 'audit-logs/' segment is appended under it (default: bifrost)","default":"bifrost"},"compress":{"type":"boolean","description":"Enable gzip compression for stored objects. Default: false","default":false}},"required":["type","bucket"],"if":{"properties":{"type":{"const":"s3"}}},"then":{"properties":{"type":true,"bucket":true,"prefix":true,"region":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"AWS region. Supports env var reference"},"endpoint":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"Custom S3-compatible endpoint for MinIO/R2. Supports env var reference"},"access_key_id":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"AWS access key ID. Omit to use default credential chain (instance role, env vars, etc.). Supports env var reference"},"secret_access_key":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"AWS secret access key. Supports env var reference"},"session_token":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"AWS session token for STS temporary credentials. Supports env var reference"},"role_arn":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"AWS IAM role ARN for STS AssumeRole. Works with static creds or instance role. Supports env var reference"},"force_path_style":{"type":"boolean","description":"Use path-style URLs for S3 (required for MinIO). Default: false","default":false},"compress":true},"dependentRequired":{"access_key_id":["secret_access_key"],"secret_access_key":["access_key_id"],"session_token":["access_key_id","secret_access_key"]},"additionalProperties":false},"else":{"properties":{"type":true,"bucket":true,"prefix":true,"credentials_json":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"GCP service account credentials JSON or file path. Omit to use Application Default Credentials. Supports env var reference"},"credentials":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"Deprecated: use credentials_json. Kept for backwards compatibility."},"project_id":{"anyOf":[{"type":"string"},{"type":"object","properties":{"value":{"type":"string"},"ref":{"type":"string"},"type":{"type":"string","enum":["plain_text","env","vault"]},"env_var":{"type":"string"},"from_env":{"type":"boolean"}},"required":["value"],"additionalProperties":false}],"description":"GCP project ID override. Supports env var reference"},"compress":true},"additionalProperties":false}}},"additionalProperties":false},"large_payload_optimization":{"type":"object","description":"Large payload streaming optimization configuration. Detects large request/response payloads and streams them through without full materialization.","properties":{"enabled":{"type":"boolean","description":"Master toggle for large payload optimization (default: false)","default":false},"request_threshold_bytes":{"type":"integer","minimum":0,"description":"Request body size in bytes above which streaming kicks in (default: 10MB)","default":10485760},"response_threshold_bytes":{"type":"integer","minimum":0,"description":"Response body size in bytes above which streaming kicks in (default: 10MB)","default":10485760},"prefetch_size_bytes":{"type":"integer","minimum":0,"description":"How much of the request to prefetch for metadata extraction in bytes (default: 64KB)","default":65536},"max_payload_bytes":{"type":"integer","minimum":0,"description":"Maximum allowed payload size in bytes; reject above this (default: 500MB)","default":524288000},"truncated_log_bytes":{"type":"integer","minimum":0,"description":"How many bytes to store as truncated preview in logs (default: 1MB)","default":1048576}},"additionalProperties":false},"websocket_config":{"type":"object","description":"Optional tuning for the WebSocket gateway (Responses API WebSocket Mode, Realtime API). WebSocket is always enabled; these fields override the high defaults.","properties":{"max_connections_per_user":{"type":"integer","minimum":1,"description":"Maximum concurrent WebSocket connections per user","default":100},"transcript_buffer_size":{"type":"integer","minimum":1,"description":"Number of transcript entries to buffer for Realtime API mid-session fallback","default":100},"pool":{"type":"object","description":"Upstream WebSocket connection pool configuration","properties":{"max_idle_per_key":{"type":"integer","minimum":1,"description":"Maximum idle connections per provider/key combination","default":50},"max_total_connections":{"type":"integer","minimum":1,"description":"Maximum total idle connections across all providers","default":1000},"idle_timeout_seconds":{"type":"integer","minimum":1,"description":"Seconds before an idle connection is evicted","default":600},"max_connection_lifetime_seconds":{"type":"integer","minimum":1,"description":"Maximum lifetime of a connection in seconds","default":7200}},"additionalProperties":false}},"additionalProperties":false},"provider_pricing_override":{"type":"object","description":"Scoped pricing override applied at runtime by the model catalog","properties":{"id":{"type":"string","description":"Unique pricing override ID"},"name":{"type":"string","description":"Human-readable name for this override"},"scope_kind":{"type":"string","description":"Scope level for this override","enum":["global","provider","provider_key","virtual_key","virtual_key_provider","virtual_key_provider_key"]},"virtual_key_id":{"type":"string","description":"Virtual key ID (required for virtual_key* scopes)"},"user_id":{"type":"string","description":"User ID (for user-scoped pricing overrides)"},"provider_id":{"type":"string","description":"Provider name (required for provider* scopes; field key remains provider_id for compatibility)"},"provider_key_name":{"type":"string","description":"Provider key name alias. Resolved to internal provider key ID at config load time."},"match_type":{"type":"string","description":"How the pattern is matched against model names","enum":["exact","wildcard"]},"pattern":{"type":"string","description":"Model name pattern to match (exact name or wildcard prefix ending with *)"},"request_types":{"type":"array","description":"Request types this override applies to. At least one value is required.","minItems":1,"items":{"type":"string"}},"pricing_patch":{"type":"string","description":"JSON-encoded pricing fields to override (e.g. '{\"input_cost_per_token\":0.000001}')"},"config_hash":{"type":"string","description":"Internal hash for change detection (auto-managed)"}},"required":["id","name","scope_kind","match_type","pattern","request_types"],"additionalProperties":false},"pricing_override_match_type":{"type":"string","enum":["exact","wildcard"]},"pricing_override_request_type":{"type":"string","enum":["chat_completion","text_completion","responses","embedding","rerank","speech","transcription","image_generation","image_variation","image_edit","video_generation","video_remix","compaction"]},"custom_provider_config":{"type":"object","description":"Custom provider configuration for extending or customizing provider behavior","properties":{"is_key_less":{"type":"boolean","description":"Whether the custom provider requires a key"},"base_provider_type":{"type":"string","enum":["openai","azure","anthropic","bedrock","bedrock_mantle","cohere","vertex","mistral","ollama","groq","opencode-go","opencode-zen","sgl","parasail","perplexity","cerebras","deepseek","gemini","openrouter","elevenlabs","huggingface","nebius","xai","replicate","vllm","runway","runware","fireworks","sarvam","wafer"],"description":"Base provider type to extend"},"request_path_overrides":{"type":"object","description":"Mapping of request type to custom path overriding the default provider path","additionalProperties":{"type":"string"}},"allowed_requests":{"type":"object","description":"Allowed request types for the custom provider","properties":{"list_models":{"type":"boolean"},"text_completion":{"type":"boolean"},"text_completion_stream":{"type":"boolean"},"chat_completion":{"type":"boolean"},"chat_completion_stream":{"type":"boolean"},"responses":{"type":"boolean"},"responses_stream":{"type":"boolean"},"responses_retrieve":{"type":"boolean","description":"GET stored response by id (OpenAI Responses lifecycle)"},"responses_delete":{"type":"boolean","description":"DELETE stored response (OpenAI Responses lifecycle)"},"responses_cancel":{"type":"boolean","description":"POST cancel in-flight stored response (OpenAI Responses lifecycle)"},"responses_input_items":{"type":"boolean","description":"GET list input items for a stored response (OpenAI Responses lifecycle)"},"count_tokens":{"type":"boolean"},"compaction":{"type":"boolean"},"embedding":{"type":"boolean"},"rerank":{"type":"boolean"},"ocr":{"type":"boolean"},"speech":{"type":"boolean"},"speech_stream":{"type":"boolean"},"transcription":{"type":"boolean"},"transcription_stream":{"type":"boolean"},"image_generation":{"type":"boolean"},"image_generation_stream":{"type":"boolean"},"image_edit":{"type":"boolean"},"image_edit_stream":{"type":"boolean"},"image_variation":{"type":"boolean"},"video_generation":{"type":"boolean"},"video_edit":{"type":"boolean"},"video_retrieve":{"type":"boolean"},"video_download":{"type":"boolean"},"video_delete":{"type":"boolean"},"video_list":{"type":"boolean"},"video_remix":{"type":"boolean"},"batch_create":{"type":"boolean"},"batch_list":{"type":"boolean"},"batch_retrieve":{"type":"boolean"},"batch_cancel":{"type":"boolean"},"batch_delete":{"type":"boolean"},"batch_results":{"type":"boolean"},"file_upload":{"type":"boolean"},"file_list":{"type":"boolean"},"file_retrieve":{"type":"boolean"},"file_delete":{"type":"boolean"},"file_content":{"type":"boolean"},"container_create":{"type":"boolean"},"container_list":{"type":"boolean"},"container_retrieve":{"type":"boolean"},"container_delete":{"type":"boolean"},"container_file_create":{"type":"boolean"},"container_file_list":{"type":"boolean"},"container_file_retrieve":{"type":"boolean"},"container_file_content":{"type":"boolean"},"container_file_delete":{"type":"boolean"},"passthrough":{"type":"boolean"},"passthrough_stream":{"type":"boolean"},"websocket_responses":{"type":"boolean"},"realtime":{"type":"boolean"},"cached_content_create":{"type":"boolean"},"cached_content_list":{"type":"boolean"},"cached_content_retrieve":{"type":"boolean"},"cached_content_update":{"type":"boolean"},"cached_content_delete":{"type":"boolean"}},"additionalProperties":false}},"required":["base_provider_type"],"additionalProperties":false},"circuit_breaker_config":{"type":"object","description":"Circuit breaker configuration for automatic failover when a provider endpoint degrades","properties":{"policies":{"type":"array","description":"List of circuit breaker policies","items":{"type":"object","description":"A single circuit breaker policy that monitors a primary provider+model and redirects to a fallback when the circuit opens","properties":{"name":{"type":"string","description":"Unique name for this policy"},"enabled":{"type":"boolean","description":"Whether this policy is active. Disabled policies are ignored by all hooks."},"primary_provider":{"type":"string","description":"Provider to monitor (e.g. 'azure', 'openai')"},"primary_model":{"type":"string","description":"Model to monitor as it appears in requests (e.g. 'gpt-4-ptu')"},"primary_key_ids":{"type":"array","description":"Optional list of key UUIDs to monitor individually. Each key gets its own sub-circuit; the main circuit opens only when all listed keys are exhausted. Leave empty to use a single shared circuit for all keys serving this provider+model.","items":{"type":"string"}},"fallback_provider":{"type":"string","description":"Provider to route to when the circuit is open"},"fallback_model":{"type":"string","description":"Model to request from the fallback provider when the circuit is open"},"condition":{"type":"object","description":"Response signals that trigger the circuit to open","properties":{"operator":{"type":"string","enum":["OR","AND"],"default":"OR","description":"OR (default): circuit opens when any signal matches. AND: circuit opens only when all signals match simultaneously."},"signals":{"type":"array","description":"List of response signals to evaluate","items":{"type":"object","properties":{"source":{"type":"string","enum":["response_header"],"description":"What part of the HTTP response to inspect"},"header_name":{"type":"string","description":"HTTP response header name to inspect"},"header_value":{"type":"string","description":"Trips when the header equals this value (case-insensitive). Mutually exclusive with header_contains."},"header_contains":{"type":"string","description":"Trips when the header value contains this substring (case-insensitive). Mutually exclusive with header_value. If neither is set, trips when the header is present."}},"required":["source","header_name"],"not":{"required":["header_value","header_contains"]},"additionalProperties":false},"minItems":1}},"required":["signals"],"additionalProperties":false},"default_cooldown":{"type":"string","pattern":"^[0-9]+(ns|us|µs|ms|s|m|h)$","description":"How long to keep the circuit open when no header-based cooldown is available. Accepts a Go duration string (e.g. '30s', '5m'). Defaults to 30s."},"cooldown_header":{"type":"string","description":"Response header whose value (in milliseconds) overrides default_cooldown (e.g. 'retry-after-ms'). Falls back to default_cooldown when absent or unparsable."}},"required":["name","primary_provider","primary_model","fallback_provider","fallback_model","condition"],"additionalProperties":false}}},"additionalProperties":false},"scim_attribute_role_mappings":{"type":"array","description":"List of attribute -> role mappings. When a user resolves to more than one role (from these mappings, the native roles claim, or group/attribute channels), roleResolutionStrategy decides which single role is assigned: 'highestPermissionCount' (default) picks the role with the most permissions; 'order' evaluates this mappings list top to bottom and the first matching rule wins.","items":{"type":"object","properties":{"attribute":{"type":"string","description":"JWT claim name (supports dot paths, e.g. 'realm_access.roles')"},"value":{"type":"string","description":"Claim value to match (case-insensitive)"},"role":{"type":"string","description":"Bifrost role to assign on match"}},"required":["attribute","value","role"],"additionalProperties":false}},"scim_role_resolution_strategy":{"type":"string","enum":["highestPermissionCount","order"],"default":"highestPermissionCount","description":"How a single role is chosen when a user resolves to more than one role: 'highestPermissionCount' (default) picks the role with the most permissions; 'order' evaluates the attribute-role mappings top to bottom and the first matching rule wins."},"scim_claims_sync_mode":{"type":"string","enum":["both","scim"],"default":"both","description":"When SCIM is enabled, selects whether IdP login/refresh claims still drive role/team/business-unit/profile sync and JIT user creation: 'both' (default) keeps syncing from both claims and SCIM; 'scim' makes SCIM the sole source of truth — claims are ignored for provisioning and new users must be pushed by SCIM. Inert when SCIM is disabled (claims always sync then)."},"scim_attribute_team_mappings":{"type":"array","description":"Attribute -> team mappings (all matches apply). Use value '*' for pass-through.","items":{"type":"object","properties":{"attribute":{"type":"string","description":"JWT claim name"},"value":{"type":"string","description":"Claim value to match, or '*' for pass-through"},"team":{"type":"string","description":"Bifrost team slug to assign. In case of '*' value, leave this empty"}},"required":["attribute","value"],"additionalProperties":false}},"scim_attribute_business_unit_mappings":{"type":"array","description":"Attribute -> business-unit mappings (all matches apply).","items":{"type":"object","properties":{"attribute":{"type":"string","description":"JWT claim name"},"value":{"type":"string","description":"Claim value to match. Wildcard '*' will use the claim as the name of the business unit to assign."},"business_unit":{"type":"string","description":"Bifrost business unit slug to assign. In case of '*' value, leave this empty"}},"required":["attribute","value"],"additionalProperties":false}}}}