Skip to content

Token exchange (self-serve keys)

Busbar lets a developer self-serve their own budgeted API key by signing in with your identity provider — no admin issuing a key per person. Busbar hosts the exchange itself at one endpoint, /auth/token. A developer signs in, gets a personal key scoped to their own budget, and points any BYOK AI tool (Cursor, VS Code, …) at it.

developer busbar (/auth/token) AI tool
───────── ──────────────────── ───────
hit /auth/token ──▶ 1. verify identity (browser sign-in
· browser, OR OR a token the caller already holds)
· a token you hold 2. map SSO group ─▶ team (role_bindings)
3. mint ONE self-scoped key
(budget from the team's child_default)
{ api_key, key_id, … } ──▶ point tool at
public_url + api_key

Re-login returns the same key. Refresh rotates it (the old key is revoked).

Same endpoint, two ways in — a developer uses whichever fits what they’re holding.

A hosted sign-in page. If you’ve enabled more than one method, it opens with a chooser (one button per method) first.

busbar hosted sign-in page

Present an IdP token you already hold (a CI job, an internal tool, az account get-access-token, …) and get the key back as JSON:

Terminal window
curl -X POST https://busbar.example.com/auth/token \
-H "Authorization: Bearer $OIDC_TOKEN"
# → {
# "api_key": "sk-bb-…", # the personal key — use as your tool's API key
# "key_id": "vk_…", # its stable id
# "group": "user:<sub>", # the personal budget bucket it charges through
# "exp": 1754006400 # expiry, Unix seconds (now + key_ttl)
# }

Identity is taken from the verified token, never from the request body — a caller can only ever mint their own key.

  1. Install an auth method plugin: OIDC, GitHub, or LDAP.
  2. Set public_url to the base developers’ browsers actually reach.
  3. Declare the provider under identity-providers: with a browser_login: block. (1.5.3: the retired parallel auth.methods: map folded into the provider definition — a client id/secret belongs to ONE IdP registration, so it belongs on that provider.)
public_url: "https://busbar.example.com" # busbar builds /auth/token against this
identity-providers:
oidc:
module: oidc
settings:
issuer: "https://login.microsoftonline.com/<tenant>/v2.0"
audience: "<client-id>"
browser_login: # presence turns on the hosted sign-in button;
client_secret: { env: OIDC_CLIENT_SECRET } # omit the block for headless-only
auth:
key_ttl: 90d # issued-key lifetime (admin-set; default 90d)
chain: [keys, oidc] # reference the provider BY BARE NAME
role_bindings:
oidc: # NESTED BY PROVIDER NAME
"<sso-group>": { group: engineering } # which team's child_default budgets each dev

That’s the shape — full per-provider config (issuer, audience, claim mapping, IdP walkthroughs) lives on the plugin page: OIDC · GitHub · LDAP.

With Entra ID, "<sso-group>" above is not necessarily your security group’s display name — what it must be depends on role_claim (app role Value string vs. security group Object ID GUID), and the redirect URI, client secret, and role/group setup have their own gotchas too. This trips people up constantly; see Walkthrough: configuring OIDC with Microsoft Entra ID for the full click-by-click reference before you bind a role.

  • One key per person. Deterministic from the verified identity, so it re-shows on every login; a Refresh button rotates it (old key revoked).
  • auth.key_ttl. Admin-set key lifetime, default 90d; a re-login re-issues within the window.
  • Self-scoped. Identity comes from the verified token, never the request body — the exchange can only mint the caller’s own user:<sub> key.
  • Budget auto-provisioned. The user:<sub> bucket is created on first exchange, limits stamped from the mapped team’s child_default.
  • Any auth method. Works identically with oidc, github, or ldap.
  • Don’t want issued keys? Put the method in auth.chain instead (e.g. chain: [oidc]) to verify a live IdP token on every request and issue no key. The two compose. See Configuration.