Recipe: Multi-Tenant Setup

Build a multi-tenant SaaS where each of your customers connects their own services through BlueNexus, with your branding.

What You'll Build

An application where:

  • Your customers sign up on your platform
  • They connect their services (Google, Slack, etc.) through a white-labeled BlueNexus flow
  • Your app accesses their connected services via MCP or REST API
  • Each customer only sees and accesses their own data

Architecture

Your SaaS App
  │
  ├── Customer A authenticates via OAuth ──→ BlueNexus (white-label)
  │     └── Customer A connects Google, Slack
  │
  ├── Customer B authenticates via OAuth ──→ BlueNexus (white-label)
  │     └── Customer B connects Jira, Notion
  │
  └── Your backend calls API with each customer's token
        ├── Customer A's token → accesses only A's Google, Slack
        └── Customer B's token → accesses only B's Jira, Notion

Step 1: Create a White-Label Auth Client

curl -X POST https://api.bluenexus.ai/api/v1/auth-clients \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "YourApp",
    "type": "confidential",
    "roles": ["whitelabel-customer"],
    "domains": ["yourapp.com"],
    "redirectUris": [
      "https://yourapp.com/auth/callback",
      "https://yourapp.com/connections/callback"
    ],
    "allowedScopes": [
      "universal-mcp-read-write",
      "agents-all",
      "llm-all",
      "connections",
      "account",
      "openid",
      "profile",
      "email"
    ],
    "branding": {
      "logo": "data:image/png;base64,YOUR_LOGO",
      "description": "YourApp — AI-powered workspace",
      "brandColor": "#4F46E5"
    }
  }'

Save the clientId and clientSecret.

Step 2: Implement OAuth in Your App

When a customer signs up or connects their BlueNexus account:

// Generate PKCE values
const codeVerifier = crypto.randomBytes(64).toString("base64url");
const codeChallenge = crypto
  .createHash("sha256")
  .update(codeVerifier)
  .digest("base64url");

// Store codeVerifier in session
session.codeVerifier = codeVerifier;
session.state = crypto.randomUUID();

// Redirect to BlueNexus (shows YOUR branding)
const url = new URL("https://app.bluenexus.ai/oauth/authorize");
url.searchParams.set("response_type", "code");
url.searchParams.set("client_id", CLIENT_ID);
url.searchParams.set("redirect_uri", "https://yourapp.com/auth/callback");
url.searchParams.set("scope", "universal-mcp-read-write connections account openid");
url.searchParams.set("state", session.state);
url.searchParams.set("code_challenge", codeChallenge);
url.searchParams.set("code_challenge_method", "S256");

res.redirect(url.toString());

Step 3: Handle the Callback

app.get("/auth/callback", async (req, res) => {
  const { code, state } = req.query;

  // Verify state
  if (state !== session.state) throw new Error("CSRF mismatch");

  // Exchange code for tokens
  const tokenRes = await fetch("https://api.bluenexus.ai/api/v1/auth/token", {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Authorization: `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`,
    },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code,
      redirect_uri: "https://yourapp.com/auth/callback",
      code_verifier: session.codeVerifier,
    }),
  });

  const { access_token, refresh_token } = await tokenRes.json();

  // Store tokens for this customer (encrypted!)
  await db.customers.update(customerId, {
    bluenexusAccessToken: encrypt(access_token),
    bluenexusRefreshToken: encrypt(refresh_token),
  });
});

Step 4: Let Customers Connect Services

Prompt customers to connect their services:

async function connectService(customerId, providerId) {
  const token = await getCustomerToken(customerId);

  const res = await fetch(
    "https://api.bluenexus.ai/api/v1/connections/initiate",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        providerId,
        redirectUrl: "https://yourapp.com/connections/success",
      }),
    }
  );

  const { authorizationUrl } = await res.json();
  // Redirect customer to authorizationUrl
}

Step 5: Access Customer Data via MCP

When your app needs to interact with a customer's services:

async function queryCustomerServices(customerId, prompt) {
  const token = await getCustomerToken(customerId);

  const res = await fetch("https://api.bluenexus.ai/mcp", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
      "X-Response-Format": "json",
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      method: "tools/call",
      params: {
        name: "use-agent",
        arguments: { prompt },
      },
      id: "1",
    }),
  });

  return res.json();
}

// Each customer's token only accesses THEIR data
await queryCustomerServices("customer-A", "What's on my calendar today?");
await queryCustomerServices("customer-B", "Show my Jira backlog");

Key Points

  • Tenant isolation is built in — each customer has their own BlueNexus account with their own connections. A customer's token can only access their data.
  • White-label branding — customers see your brand, not BlueNexus, throughout the OAuth and connection management flows.
  • Token management — store and refresh tokens for each customer. Use the refresh token grant when access tokens expire.
  • Credit billing — set billingAccountType: "owner" on agents if you want to absorb credit costs, or "user" to charge per-customer.