Identity Bridge
Link machine-registered tenants to dashboard accounts
Identity Bridge
The Identity Bridge connects BrainstormRouter's two registration paths: machine registration (POST /v1/register) and OAuth provisioning (POST /auth/provision). It enables users who set up tenants via API to manage them from the dashboard.
How It Works
- Machine registration returns a
claimobject with a token and dashboard URL - Open the URL in a browser and sign in with GitHub/Google
- The dashboard automatically links your OAuth identity to the machine-registered tenant
- You can now manage that tenant's API keys, config, guardrails, and analytics from the dashboard
Claim Token Flow
Every POST /v1/register response includes a claim token:
{
"tenant": { "id": "...", "slug": "my-lab" },
"api_key": { "key": "br_live_..." },
"claim": {
"token": "brct_...",
"url": "https://brainstormrouter.com/dashboard/?claim=brct_...",
"expires_at": "2026-04-14T00:00:00Z"
}
}
Generate Additional Claim Links
Tenant admins can generate invite links for teammates:
const link = await client.identityBridge.generateClaimLink({
role: "admin", // owner | admin | member
maxUses: 5, // how many people can use this link
expiresInDays: 7,
});
console.log(link.url); // Share with teammate
link = client.identity_bridge.generate_claim_link(
role="admin",
max_uses=5,
expires_in_days=7,
)
print(link["url"])
curl -X POST https://api.brainstormrouter.com/v1/tenant/claim-link \
-H "Authorization: Bearer br_live_..." \
-H "Content-Type: application/json" \
-d '{"role": "admin", "max_uses": 5, "expires_in_days": 7}'
Device Flow
For terminal-to-browser linking without copying tokens:
// 1. Agent starts device flow
const { user_code, verification_uri } = await client.identityBridge.authorizeDevice();
console.log(`Go to ${verification_uri} and enter: ${user_code}`);
// 2. Poll until user confirms in browser
let result;
do {
await new Promise((r) => setTimeout(r, 5000));
result = await client.identityBridge.pollDeviceToken(device_code);
} while (result.error === "authorization_pending");
if (result.status === "linked") {
console.log("Linked to tenant:", result.tenant.slug);
}
import time
# 1. Start device flow
auth = client.identity_bridge.authorize_device()
print(f"Go to {auth['verification_uri']} and enter: {auth['user_code']}")
# 2. Poll
while True:
time.sleep(auth["interval"])
result = client.identity_bridge.poll_device_token(auth["device_code"])
if result.get("status") == "linked":
print(f"Linked to tenant: {result['tenant']['slug']}")
break
if result.get("error") == "expired_token":
print("Code expired")
break
Multi-Tenant Management
After claiming multiple tenants, pass the X-BR-Tenant-Id header to select which tenant context to use:
curl https://api.brainstormrouter.com/auth/me \
-H "Authorization: Bearer <supabase-jwt>" \
-H "X-BR-Tenant-Id: <tenant-uuid>"
The dashboard handles this automatically via a tenant switcher.
Role Mapping
| Claim Role | Dashboard Permission | users.role |
|---|---|---|
owner | Full access + can create claim tokens | admin |
admin | Full access | admin |
member | Standard access | developer |
API Reference
POST /v1/tenant/claim-link
Generate a claim link. Requires API key with admin scope.
POST /v1/auth/device/authorize
Start device authorization flow. Returns device_code and user_code.
POST /v1/auth/device/token
Poll device flow status. Returns authorization_pending, expired_token, or linked.
POST /v1/auth/link
Generate a browser link URL (7-day expiry, single use).
POST /auth/claim
Redeem a claim token (Supabase JWT auth). Creates per-tenant actor + membership.
POST /auth/device/confirm
Confirm a device code from the browser (Supabase JWT auth).
Collision Policies
| Scenario | Behavior |
|---|---|
| Same identity claims same tenant twice | 409 Conflict |
| Different email uses claim token | Works (token is proof-of-possession) |
| Identity already has another tenant | Both coexist (multi-tenant) |
| Claim token used up | 400 "fully redeemed" |
| Expired token | 400 "expired" |