Skip to content

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.

config.yaml busbar 1.5.3
# 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 } }
  • 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:, and store: (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, and cheapest need a definition entry only when they carry config, like admin-tokens holding its token: secret reference above.
  • Every secret is a reference. { env: VAR }, { file: /path }, or { module: <secret-plugin> }. A secret value never appears inline in config.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 named hooks or upstream_credentials.)
  • Combine. Lists are additive: pools.hooks and a pool’s own hooks: are unioned. Scalars override: a pool’s upstream_credentials replaces 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.

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.

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:

RetiredNow
global_hooks:the top-level hooks: definition map + pools.hooks: [names]
an inline hook instance in a hooks: lista hooks: definition, referenced by bare name
an inline identity-provider entry in auth.chain / auth.admin_authan identity-providers: definition, referenced by bare name
auth.methods:the matching identity-providers: definition (browser_login: is per-provider)
auth.upstream_credentialspools.upstream_credentials + a per-pool override
observability:the export: named map
observability.otlp_url / otlp_endpointan export: instance with module: otlp
observability.request_log_webhook_urlan export: instance with module: request-log-webhook
metrics:an export: instance with module: prometheus
admin_insecure: trueadmin_require_mtls: false (inverted, so the safe posture is the default)
a tap’s at: route / at: attempt / at: completionphase: [candidate] / [routing] / [response]; phase: is a list