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

  1. Machine registration returns a claim object with a token and dashboard URL
  2. Open the URL in a browser and sign in with GitHub/Google
  3. The dashboard automatically links your OAuth identity to the machine-registered tenant
  4. 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 RoleDashboard Permissionusers.role
ownerFull access + can create claim tokensadmin
adminFull accessadmin
memberStandard accessdeveloper

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

ScenarioBehavior
Same identity claims same tenant twice409 Conflict
Different email uses claim tokenWorks (token is proof-of-possession)
Identity already has another tenantBoth coexist (multi-tenant)
Claim token used up400 "fully redeemed"
Expired token400 "expired"