Busbar's scope is defined by wire protocols, not by a hand-maintained list of vendor integrations. It implements a small set of protocols losslessly:
| Protocol | Surface | Auth shape |
|---|---|---|
anthropic |
/v1/messages |
x-api-key (API key, sk-ant-api…) or Authorization: Bearer (OAuth, sk-ant-oat…); unrecognized credentials get both. See configuration.md. |
openai |
/v1/chat/completions |
bearer |
responses |
/v1/responses |
bearer |
gemini |
:generateContent / :streamGenerateContent |
api-key header (x-goog-api-key) |
bedrock |
Converse / ConverseStream | per-request AWS SigV4 |
cohere |
v2 /v2/chat |
bearer |
Any provider that speaks one of these is a catalog entry in providers.yaml
(a name, a base_url, an env var for its key, an optional path override): no
code. A client speaking any protocol can target any provider; Busbar translates
through its superset IR when the two differ.
This is why the number to watch is the protocol count (6), not the provider count. The shipped catalog is a curated convenience set of verified hosted endpoints, verified, not scraped, because each entry's error-code mappings feed the breaker's fault attribution. An operator can point Busbar at any OpenAI-compatible endpoint, including their own, with three lines of YAML and no wait for an "integration." We deliberately don't chase a giant provider count; that's a maintenance treadmill that dilutes the vetting.
A provider integration is two things: a protocol (request/response shape) and
an auth method. Busbar separates them. The ProtocolWriter trait exposes two
hooks (crates/busbar/src/proto/mod.rs):
auth_headers(key): static headers (bearer, api-key header, …).sign_request(key, ctx), per-request signing, given method/host/path/body/time.
Bedrock overrides sign_request to compute SigV4 from ACCESS_KEY:SECRET[:SESSION]
with the region parsed from the host. This already proves the architecture is not
bearer-only: today there are four distinct auth shapes in production: bearer,
x-goog-api-key, SigV4, and a per-provider auth: api-key override (Azure OpenAI).
The per-provider path override (for version-in-base-url endpoints) is another piece
of the same flexibility.
So "non-standard auth/path" backends are not a categorical exclusion, they are the next auth adapters on a seam that already exists.
- Cohere v2 (
/v2/chat): the Command family natively, as the 6th protocol (request/response/streaming Reader + Writer, bearer auth). System prompts are canonicalized into the IR so they survive cross-protocol translation.
- Active health checks: a per-provider
health:block (mode: none|dead|active,interval_secs,timeout_secs).deadre-probes only tripped lanes for prompt recovery;activeprobes every lane so a silently-dead upstream trips before real traffic hits it. Probes reuse each protocol'sprobe_body, so all six protocols work with no per-protocol code. - Per-pool circuit-breaker config: a pool's
breaker:block (trip.modeerror_rate|consecutive, window/threshold/min_requests/n, base/max cooldown) now drives the trip decision instead of a hardcoded rule. failover.exclusions: members named there are removed from a pool's candidate set entirely (never primary or failover).- Configurable affinity header: a pool's
affinity.header_name(defaultx-session-id). - Breaker recovery fix: a tripped lane now completes recovery to Closed on a successful half-open probe (previously it could become permanently dead).
- Per-(pool, lane) circuit-breaker isolation: a lane shared across pools now
carries independent breaker state per pool, so one pool tripping a lane no longer
benches it for the others. Concurrency and
max_requestsbudget stay shared (one upstream); a successful health probe recovers the lane in every pool.
- The cost model: tokens are the ledger, dollars are derived. Enforcement accumulates a
per-(bucket, window, model, tier) token ledger; every spend figure is computed at read time from
that ledger and the top-level
rate_card(per-model, per-tier micro-unit rates in an abstract cost unit). Nothing dollar-shaped is stored, so correcting a rate is a config edit and reload, not a re-billing. The old flat per-1k-token price is gone;rate_cardis the only token-pricing mechanism, withper_request_feeas a separate flat per-call fee. - The
groups:limit tree. Nestable enforcement buckets — the ONE place limits live (keys are pure auth and carry none). A key binds one with the mint fieldgroup; admission walks the whole parent chain (any depth — the arbitrary 8-level ceiling is gone; the cycle check bounds the walk), ANDs every limit, and the 429 names the exhausted bucket. Mint-time keylabelsride onto the Prometheus series so external dashboards break spend down by any operator dimension. A group may carry achild_defaultlimit template: the first auto-provisioned child under a group inherits those limits (nearest-ancestor wins), so a org/team/user hierarchy stamps per-user budgets automatically. - Pool-qualified limits and
on_exhaust: downgrade. A windowed limit may carrypool: <name>to account per(group, pool)instead of group-wide: one team's expensive-tier budget is independent from their cheap-tier budget. A pool-scopedbudgetlimit may also declareon_exhaust: downgrade, downgrade_to: <pool>— when it runs dry, the request is re-admitted through the cheaper pool instead of refused (the caller's expensive calls get cheaper, not blocked). - Runtime-mutable groups on the Admin API.
GET/POST/PUT/PATCH/DELETE /api/v1/admin/groupsandGET /groups/{name}/usage(per-(window, pool) usage vs. caps, repriced at read time) andGET /keys?group=<name>(the keys bound to a group). A write is validate-at-the-door, then live on the next request; the ledger survives the swap.PATCHis the ergonomic "raise Alice's budget" and "freeze a team" verb. - Self-service mint: auto-provision + the
mintscope.POST /keysaccepts an optionalparent: whengroupnames a leaf that does not yet exist, it is auto-provisioned underparent(limits fromchild_default). A new delegatedmintadmin scope — sibling ofhooks-register, NOT a ladder rung above it — lets a self-service portal mint keys without god-modefull.limits.max_keys_per_principalcaps keys per group (per-user anti-sprawl). - Per-section overlay reset.
DELETE /api/v1/admin/overlay/{section}(sectiongroups|hooks) discards all overlay mutations for that section and reverts it to baseconfig.yamltruth, leaving the other section's runtime mutations untouched. - Enforcement is always on. There is no
governance:block or enabled switch. Enforcement is always present and simply inert until keys are minted, so a default deploy behaves as "off" did with the same RAM. Durability is a choice via the top-levelstore:block (memorydefault; durable backends load as signed plugins). - Dynamic plugins. Store, auth, and hook backends can load from a signed
.tar.gzat boot over a versioned C ABI, gated by theplugins.*block (off by default, ed25519 signature verification against the embedded release key, explicit opt-ins for unsigned or third-party). The default binary is leaner for it: the SQLite store now ships as a droppable plugin rather than compiled in.
These reuse existing protocols (no new wire format) gated behind an auth shim: the same pattern Bedrock established with SigV4 and Azure OpenAI (shipped in 0.14):
- Google Vertex AI: shipped in 1.4, both Gemini-on-Vertex (
geminiprotocol) and Claude-on-Vertex (anthropicprotocol), at a project/location- scoped URL (path_base) authed with the newauth: jwt-bearerOAuth adapter (a short-lived bearer minted from a service-account key via the RFC 7523 JWT-bearer grant, and auto-refreshed). Each is aproviders.yamlentry, no new protocol. Claude-on-Vertex additionally moves the model into the URL (:rawPredict) and adds theanthropic_versionbody field, both handled automatically. - Databricks Foundation Model APIs:
openaiprotocol with bearer auth, but thebase_urlis workspace-specific (https://<workspace>/serving-endpoints), so it is added by the operator as their own host rather than shipped in the verified catalog. Supportable today via a config entry +pathoverride; will be documented as a recipe.
Streaming failover today is bounded by the first byte reaching the client (see failover). A configurable pre-release buffer would hold the first K tokens / T ms of an upstream stream before releasing any byte, so a provider that dies inside that window can still be rerouted invisibly. Costs up to T ms of added TTFT, so it will be opt-in per pool and default to off (today's behavior). Not yet built; no config surface exists for it.
The SemVer-stable contract is the RUNTIME: the data-plane HTTP surface and the six wire protocols do
not break without a major-version bump. The config format is an operator deployment artifact outside
that freeze: it may change between releases, always with a migration path (busbar --migrate-config)
and a loud fail-closed boot on an outdated config. The admin API carries its own contract version
(/api/v1/admin).