# Authentication

> All caller traffic uses `Authorization: Bearer <token>`. The token is a Switchboard key (`sb_…`) or a short-lived JWT from customer OAuth.

## Keys

| Kind | Prefix (typical) | Use |
|---|---|---|
| App | `sb_live_app_…` / `sb_test_app_…` | First-party backends |
| Agent | `sb_live_agt_…` / `sb_test_agt_…` | MCP / tool loops; approvals on side effects |
| Customer | `sb_live_cus_…` / `sb_test_cus_…` / OAuth client | External developers |
| Sys | `sb_live_sys_…` | Platform operators / console BFF (admin routes only) |

### Live vs test ring

The second segment of the token is the **ring**:

| Ring | Prefix | Behavior |
|---|---|---|
| `live` | `sb_live_…` | Real provider egress, Axis billing, prepaid credits |
| `test` | `sb_test_…` | Sandbox / simulator — no live Axis billing; connectors with a `sandbox` (or `test`) egress region are forced there; otherwise the cell’s `SB_SIMULATOR_URL` / `-simulator` base is used; virtual LLM ops prefer the `echo` harness |

Mint with `env: "test"` (Control Room toggle, control plane `POST /v1/keys`, or `mintkey -env test`). Local smoke token: `sb_test_app_sandbox_devsecret01`.

Mint in **Control Room** (or control plane `POST /v1/keys`), store the one-time secret, then **publish LKG** so cells load it.

Scopes decide which connectors and models the key may hit (`openai.*`, `stripe.*`, `switchboard` + `anybody|thinker|expert`, family scopes like `ai.*`). Out of scope → `403` `forbidden_scope`.

## Request

```http
Authorization: Bearer sb_live_…
# or
Authorization: Bearer sb_test_…
```

Optional attribution headers: `SB-Feature`, `SB-Customer`, `SB-Region` — see [headers.md](./headers.md).

## Customer OAuth (client credentials)

```bash
curl -sS "$SWITCHBOARD_BASE_URL/oauth/token" \
  -d grant_type=client_credentials \
  -d client_id=key_cus_test \
  -d client_secret=…
# → { "access_token", "token_type":"Bearer", "expires_in": 900 }
```

Use the JWT as the Bearer token. It inherits the customer key’s scopes / namespace.

## Local smoke key

With `make run-gateway`, mint or load a key from `platform/testdata/keys/` (never commit production secrets). Shape:

```text
sb_live_app_<slug>_<secret>
```

Local testdata scopes are wide — do not reuse those secrets in production.

## What callers must never hold

When Switchboard owns the hop: OpenAI, Anthropic, Stripe, Twilio, Buffer, … provider secrets stay in Vault / cell secrets (house or BYOK). Product repos only need `SB_KEY` + cell URL(s).
