Config at a glance
One whole, realistic config.yaml on a single page. It is a map, not a reference: every key,
module name, and notable enum below is a link to the spec for that thing, so you can see the shape
of a complete deployment and click straight through to the detail for any part of it. For the
per-field tables with defaults and validation rules, use Configuration.
The block copy-pastes. What you select is plain YAML (the links are markup, not text), so
copying it gives you a byte-for-byte valid file. Fill in your own provider keys and it boots.
Check it first with busbar --validate config.yaml, which runs the exact parse the boot path runs.
# 1. PLUGINS: the extension gate. Nothing loads until this is on.
plugins:
enabled: true
dir: plugins
trust: { publishers: [], allow_unsigned: false, allow_third_party: false }
min_versions: {}
# 2. PLUGIN INSTANCES: define once, reference by name.
# Every kind is the same shape: name -> { module, settings, ... }. The NAME is the instance;
# module: is which plugin backs it. Built-ins (keys, admin-tokens, cheapest) are referenced
# bare and only need an entry here when they carry config.
store: { module: memory }
identity-providers:
admin-tokens: { module: admin-tokens, token: { env: BUSBAR_ADMIN_TOKEN } }
corp-ad: { module: ad, settings: { server: "ldaps://corp", base_dn: "dc=corp" }, max_admin_scope: read-only }
hooks:
audit: { module: busbar-audit, kind: tap, prompt: no, on_error: nothing }
pii: { module: busbar-phi, groups: [engineering], phase: [request, response], kind: gate, prompt: ro, user: no, on_error: reject }
smart-router: { module: smart-router-hook, kind: gate, prompt: no, user: no, on_error: weighted }
export:
# streams: is this sink's PROJECTION, what it is entitled to, named per instance. Omit it and
# the instance takes the streams its module carries. fields: narrows further and is not reachable
# with the built-in modules in 1.5.3: set streams:, omit fields:.
metrics: { module: prometheus, streams: [metrics], settings: { buffer_seconds: 60 } }
req-log: { module: request-log-webhook, streams: [logs], settings: { url: "https://logs.example.com/busbar" } }
req-siem: { module: request-log-webhook, streams: [logs], settings: { url: "https://siem.internal/ingest" } }
traces: { module: otlp, streams: [traces], settings: { url: "http://localhost:4318/v1/traces" } }
# 3. TRANSPORT & IDENTITY
listen: "0.0.0.0:8080"
admin_listen: "127.0.0.1:8081"
admin_require_mtls: true
auth:
signing_key: { file: /run/secrets/busbar-signing.key }
key_ttl: 90d
chain: [keys, corp-ad]
admin_auth: [admin-tokens, corp-ad]
role_bindings: { corp-ad: { growth-eng: { allowed_pools: [fast], group: growth }, platform: { group: acme, admin_scope: full } } }
groups:
acme: { limits: [ { requests: 500, per: minute }, { budget: 1000000, per: month } ] }
growth: { parent: acme, limits: [ { requests: 50, per: minute }, { budget: 200000, per: month } ], child_default: { limits: [ { budget: 2000, per: month } ] } }
user:bob: { parent: growth, limits: [ { requests: 10, per: minute }, { concurrent: 5 } ] }
# 4. UPSTREAMS & PRICING
providers: { anthropic: { api_key: { env: ANTHROPIC_API_KEY } }, bedrock: { api_key: { env: AWS_BEDROCK_CREDS } } }
models: { sonnet-anthropic: { provider: anthropic, upstream_model: claude-3-5-sonnet }, sonnet-bedrock: { provider: bedrock, upstream_model: anthropic.claude-3-5-sonnet-v1 } }
rate_card: { sonnet-anthropic: { input_utok: 3.0, output_utok: 15.0 }, sonnet-bedrock: { input_utok: 2.8, output_utok: 14.0 } }
per_request_fee: 0
# 5. ROUTING: reference models and hooks by name.
pools:
hooks: [audit] # all-pools attach: a LIST, so it is ADDITIVE with each pool's own
upstream_credentials: own # all-pools default: a SCALAR, so a pool OVERRIDES it
fast:
upstream_credentials: passthrough
members: [ { model: sonnet-anthropic, weight: 3, tier: primary }, { model: sonnet-bedrock, weight: 1, tier: overflow, tags: [baa] } ]
hooks: [cheapest, pii, smart-router]
breaker: { base_cooldown_secs: 15, max_cooldown_secs: 120, trip: { mode: error_rate, window_secs: 30, threshold: 0.5, min_requests: 5, consecutive_n: 3 } }
failover: { timeout_secs: 120, max_hops: 3, exclusions: [] }
on_exhausted: reject
affinity: { mode: session }
cold:
members: [ { model: sonnet-bedrock, weight: 1 } ]
hooks: [cheapest]
# 6. TUNING
security: { blocked_metadata_hosts: [], allow_metadata_hosts: [], allow_all_metadata: false }
limits: { upstream_request_timeout_secs: 300, request_body_max_bytes: 33554432, max_inbound_concurrent: 8192, hard_down_cooldown_secs: 1800, max_keys_per_principal: 50, reasoning_effort_budgets: { minimal: 1024, low: 4096, medium: 8192, high: 16384 } }
advanced: { rate_sweep_interval: 256, usage_flush_interval_ms: 100, response_headers: { server_timing: false, route_policy: false } }
The rules the whole surface follows
Section titled “The rules the whole surface follows”- Define once, reference by name. Every plugin-instance kind is a top-level named definition
map (
name -> { module, settings, … }) and is referenced by bare name everywhere else. There are four:hooks:,identity-providers:,export:, andstore:(singular; you can only have one). The name is the instance;module:is which plugin backs it. The same module can back several names when they need different scopes or settings. - Built-ins are referenced bare.
keys,admin-tokens, andcheapestneed a definition entry only when they carry config, likeadmin-tokensholding itstoken:secret reference above. - Every secret is a reference.
{ env: VAR },{ file: /path }, or{ module: <secret-plugin> }. A secret value never appears inline inconfig.yaml. - Placement. A setting that applies to every entity in one section lives at that section’s
level as a reserved key (
pools.hooks,pools.upstream_credentials) and is overridable per-entity. (A pool may therefore not be namedhooksorupstream_credentials.) - Combine. Lists are additive:
pools.hooksand a pool’s ownhooks:are unioned. Scalars override: a pool’supstream_credentialsreplaces the all-pools default. - An omitted list means “all”; an explicit
[]means “none”. Windows are nouns (minute|hour|day|month|total). Unknown keys fail boot: a typo is never silently ignored.
What is not shown
Section titled “What is not shown”The MCP tools: and A2A agents: registries are projected, not shipped: they land in 1.5.4 and
1.5.6 respectively, and the 1.5.3 binary rejects them as unknown keys. They follow exactly the model
above: a registry of named upstreams, each with its own hooks: [names], plus a section-level
tools.hooks / agents.hooks all-entities attach. The smart-router.quality block (1.5.5) is the
same story; the smart router itself is already just a hooks: entry, shown above.
Coming from an older config
Section titled “Coming from an older config”1.5.3 is the break-once release: after it, the config grammar is additive-only. If you are
upgrading, busbar --migrate-config config.yaml rewrites every mechanical change and prints a TODO
comment wherever a human has to decide. The keys it retires:
| Retired | Now |
|---|---|
global_hooks: | the top-level hooks: definition map + pools.hooks: [names] |
an inline hook instance in a hooks: list | a hooks: definition, referenced by bare name |
an inline identity-provider entry in auth.chain / auth.admin_auth | an identity-providers: definition, referenced by bare name |
auth.methods: | the matching identity-providers: definition (browser_login: is per-provider) |
auth.upstream_credentials | pools.upstream_credentials + a per-pool override |
observability: | the export: named map |
observability.otlp_url / otlp_endpoint | an export: instance with module: otlp |
observability.request_log_webhook_url | an export: instance with module: request-log-webhook |
metrics: | an export: instance with module: prometheus |
admin_insecure: true | admin_require_mtls: false (inverted, so the safe posture is the default) |
a tap’s at: route / at: attempt / at: completion | phase: [candidate] / [routing] / [response]; phase: is a list |