HomeDocsExternal Facing Agents Setup Guide

External Facing Agents Setup Guide

This guide explains how to integrate Agentic Fabriq's B2B2C (Business-to-Business-to-Consumer) feature into your application. B2B2C allows you to build consumer-facing AI agents where your users connect their own tools (Gmail, Slack, etc.) without needing Agentic Fabriq accounts.

Overview

What B2B2C Does

Normally, every user who wants to connect their Gmail or Slack to an AI agent needs to create an Agentic Fabriq account. B2B2C removes this requirement.

With B2B2C enabled:

  • Your app manages its own users (you handle login, signup, etc.)
  • Agentic Fabriq stores tool connections "invisibly" under your user IDs
  • Your users never see or interact with Agentic Fabriq directly
  • Your AI agent can use tools on behalf of any of your users

Architecture

Your Users  →  Your App  →  Agentic Fabriq  →  Gmail/Slack/etc.
                   ↑              ↑
              (your auth)    (hidden from users)

Prerequisites

Before starting, you need:

  1. An Agentic Fabriq organization account with admin access
  2. A registered application with B2B2C mode enabled
  3. Your app credentials: app_id and app_secret
  4. A backend server that can make HTTPS requests
  5. A callback URL on your domain (e.g., https://yourapp.com/oauth/callback)

Initial Setup

Step 1: Register Your Application

  1. Go to the Agentic Fabriq admin console
  2. Navigate to Admin Console → Applications
  3. Click Register Application
  4. Fill in:
    • Application ID: A unique identifier (e.g., my-ai-assistant)
    • Enable B2B2C Mode: Check this box
    • OAuth Callback URL: Your callback URL (e.g., https://yourapp.com/oauth/callback)
  5. Select the provider scopes your app needs (e.g., Gmail, Slack)
  6. Complete the registration and activation process
  7. Save your app_id and app_secret securely

Step 2: Store Credentials

Store these in your environment variables (never in code):

# .env
AF_API_URL=https://staging.agenticfabriq.com
AF_APP_ID=org-abc123_my-ai-assistant
AF_APP_SECRET=your-secret-key-here

Backend Integration

Your backend handles three main responsibilities:

  1. Initiating OAuth flows for users
  2. Handling OAuth callbacks
  3. Getting MCP tokens for your AI agent

Project Structure

your-app/
├── config/
│   └── agentic_fabric.py      # AF configuration
├── services/
│   └── agentic_fabric.py      # AF API client
├── routes/
│   ├── integrations.py        # OAuth routes
│   └── agent.py               # AI agent routes
└── main.py

Configuration

# config/agentic_fabric.py

import os
from dataclasses import dataclass

@dataclass
class AgenticFabricConfig:
    api_url: str
    app_id: str
    app_secret: str
    
    @classmethod
    def from_env(cls) -> "AgenticFabricConfig":
        return cls(
            api_url=os.environ["AF_API_URL"],
            app_id=os.environ["AF_APP_ID"],
            app_secret=os.environ["AF_APP_SECRET"],
        )

# Global instance
af_config = AgenticFabricConfig.from_env()

Agentic Fabriq Client Service

# services/agentic_fabric.py

import httpx
from typing import Optional, Dict, Any, List
from config.agentic_fabric import af_config

class AgenticFabricClient:
    """Client for Agentic Fabriq B2B2C API."""
    
    def __init__(self):
        self.base_url = af_config.api_url
        self.app_id = af_config.app_id
        self.app_secret = af_config.app_secret
    
    def _get_headers(self) -> Dict[str, str]:
        """Get authentication headers for AF API calls."""
        return {
            "Content-Type": "application/json",
            "X-App-Id": self.app_id,
            "X-App-Secret": self.app_secret,
        }
    
    async def initiate_oauth(
        self, 
        external_user_id: str, 
        provider: str,
        connection_id: str,
    ) -> Dict[str, Any]:
        """
        Start OAuth flow for an external user.
        
        Args:
            external_user_id: Your internal user ID (string, max 255 chars)
            provider: Tool provider (gmail, slack, github, notion)
            connection_id: Identifier for this connection (required)
            
        Returns:
            Dict with 'oauth_url' to redirect the user to
        """
        url = f"{self.base_url}/api/v1/apps/{self.app_id}/external-users/{external_user_id}/oauth/{provider}/initiate"
        
        payload = {"connection_id": connection_id}
        
        async with httpx.AsyncClient() as client:
            response = await client.post(
                url,
                headers=self._get_headers(),
                json=payload,
                timeout=30.0,
            )
            response.raise_for_status()
            return response.json()
    
    async def get_user_connections(
        self, 
        external_user_id: str
    ) -> List[Dict[str, Any]]:
        """Get list of connected tools for an external user."""
        url = f"{self.base_url}/api/v1/apps/{self.app_id}/external-users/{external_user_id}/connections"
        
        async with httpx.AsyncClient() as client:
            response = await client.get(
                url,
                headers=self._get_headers(),
                timeout=30.0,
            )
            response.raise_for_status()
            return response.json()
    
    async def get_mcp_token(
        self, 
        external_user_id: str,
    ) -> Dict[str, Any]:
        """
        Get an MCP JWT token for an external user.
        
        This token allows your AI agent to call MCP tools
        using this user's connected credentials.
        """
        url = f"{self.base_url}/api/v1/apps/{self.app_id}/external-users/{external_user_id}/token"
        
        async with httpx.AsyncClient() as client:
            response = await client.post(
                url,
                headers=self._get_headers(),
                json={},
                timeout=30.0,
            )
            response.raise_for_status()
            return response.json()

# Global instance
af_client = AgenticFabricClient()

OAuth Routes

# routes/integrations.py

from fastapi import APIRouter, Request, HTTPException
from fastapi.responses import RedirectResponse
from services.agentic_fabric import af_client
from auth import get_current_user  # Your auth system

router = APIRouter(prefix="/integrations", tags=["integrations"])


@router.post("/connect/{provider}")
async def initiate_connection(provider: str, request: Request):
    """User clicks "Connect Gmail/Slack/etc" button."""
    user = await get_current_user(request)
    
    valid_providers = ["gmail", "slack", "github", "notion", "google_calendar", "google_drive"]
    if provider not in valid_providers:
        raise HTTPException(status_code=400, detail=f"Invalid provider: {provider}")
    
    try:
        result = await af_client.initiate_oauth(
            external_user_id=str(user.id),
            provider=provider,
            connection_id=f"{provider}_{user.id}",
        )
        return {"oauth_url": result["oauth_url"]}
        
    except httpx.HTTPStatusError as e:
        raise HTTPException(
            status_code=e.response.status_code,
            detail=f"Failed to initiate OAuth: {e.response.text}"
        )


@router.get("/callback")
async def oauth_callback(
    status: str,
    external_user_id: str,
    tool: str,
    connection_id: str = None,
    error: str = None,
):
    """Agentic Fabriq redirects users here after OAuth completes."""
    if status == "success":
        return RedirectResponse(
            url=f"/settings/integrations?connected={tool}",
            status_code=302,
        )
    else:
        error_msg = error or "Unknown error"
        return RedirectResponse(
            url=f"/settings/integrations?error={error_msg}",
            status_code=302,
        )


@router.get("/connections")
async def list_connections(request: Request):
    """Get list of connected tools for the current user."""
    user = await get_current_user(request)
    
    try:
        connections = await af_client.get_user_connections(
            external_user_id=str(user.id)
        )
        return {"connections": connections}
        
    except httpx.HTTPStatusError as e:
        if e.response.status_code == 404:
            return {"connections": []}
        raise HTTPException(
            status_code=e.response.status_code,
            detail=f"Failed to get connections: {e.response.text}"
        )

Frontend Integration

Your frontend needs to:

  1. Show which tools are connected
  2. Provide buttons to connect/disconnect tools
  3. Handle the OAuth redirect flow

React Example

// components/IntegrationSettings.tsx

import { useState, useEffect } from 'react';

interface Connection {
  tool: string;
  connection_id: string;
  status: string;
  created_at: string;
}

export function IntegrationSettings() {
  const [connections, setConnections] = useState<Connection[]>([]);
  const [loading, setLoading] = useState(true);
  const [connecting, setConnecting] = useState<string | null>(null);

  const integrations = [
    { id: 'gmail', name: 'Gmail', icon: '📧', description: 'Send and read emails' },
    { id: 'slack', name: 'Slack', icon: '💬', description: 'Send messages to channels' },
    { id: 'github', name: 'GitHub', icon: '🐙', description: 'Manage repositories and issues' },
    { id: 'notion', name: 'Notion', icon: '📝', description: 'Access your Notion workspace' },
  ];

  useEffect(() => {
    loadConnections();
  }, []);

  async function loadConnections() {
    try {
      const response = await fetch('/api/integrations/connections');
      const data = await response.json();
      setConnections(data.connections || []);
    } catch (err) {
      console.error('Failed to load connections:', err);
    } finally {
      setLoading(false);
    }
  }

  async function connectTool(provider: string) {
    setConnecting(provider);
    
    try {
      const response = await fetch(`/api/integrations/connect/${provider}`, {
        method: 'POST',
      });
      const data = await response.json();
      
      // Redirect user to OAuth page
      window.location.href = data.oauth_url;
      
    } catch (err) {
      console.error('Failed to start OAuth:', err);
      setConnecting(null);
    }
  }

  function isConnected(provider: string): boolean {
    return connections.some(c => c.tool === provider && c.status === 'active');
  }

  if (loading) {
    return <div>Loading integrations...</div>;
  }

  return (
    <div className="space-y-6">
      <h2 className="text-xl font-semibold">Connected Integrations</h2>
      <div className="space-y-4">
        {integrations.map(integration => {
          const connected = isConnected(integration.id);
          const isConnecting = connecting === integration.id;
          
          return (
            <div key={integration.id} className="flex items-center justify-between p-4 border rounded-lg">
              <div className="flex items-center gap-4">
                <span className="text-2xl">{integration.icon}</span>
                <div>
                  <h3 className="font-medium">{integration.name}</h3>
                  <p className="text-sm text-gray-500">{integration.description}</p>
                </div>
              </div>
              
              <div>
                {connected ? (
                  <span className="text-green-600 text-sm">✓ Connected</span>
                ) : (
                  <button
                    onClick={() => connectTool(integration.id)}
                    disabled={isConnecting}
                    className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
                  >
                    {isConnecting ? 'Connecting...' : 'Connect'}
                  </button>
                )}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

Agent Integration

Your AI agent needs to:

  1. Get an MCP token for the user
  2. Call MCP tools using that token

How the Token Works

When you call the /token endpoint to get an MCP token for an external user, Agentic Fabriq creates a JWT with special claims:

{
  "user_id": "ext:sarah_123",
  "external_user_id": "sarah_123",
  "is_external_user": true,
  "app_id": "org-xxx_myapp",
  ...
}

The key field is is_external_user: true. When your agent calls MCP tools with this token, AF's MCP server automatically routes to the external user's credentials. You don't need to do anything special - just pass the token to MCP calls.

Using the SDK (Recommended)

# services/mcp_client.py (SDK version)

from af_sdk import MCPClient
from config.agentic_fabric import af_config
from services.agentic_fabric import af_client

async def create_mcp_client(external_user_id: str) -> MCPClient:
    """
    Create an MCPClient for a B2B2C external user.
    
    Args:
        external_user_id: Your system's user ID
        
    Returns:
        MCPClient ready to call tools
    """
    # Get MCP token from AF
    token_response = await af_client.get_mcp_token(
        external_user_id=external_user_id,
    )
    
    # Create client with method="token" for B2B2C
    return MCPClient(
        method="token",
        app_id=af_config.app_id,
        app_secret=af_config.app_secret,
        af_token=token_response["access_token"],
        gateway_url=af_config.api_url,
    )

# Usage in your agent:
async def use_tools_for_user(external_user_id: str):
    async with await create_mcp_client(external_user_id) as client:
        # List available tools
        tools = await client.list_tools()
        
        # Call a tool
        result = await client.call_tool(
            "gmail_send_email",
            {"to": "bob@example.com", "subject": "Hi", "body": "Hello!"}
        )
        return result

B2B2C API Reference

Base URL

https://api.agenticfabriq.com/api/v1

# For staging/development:
https://staging.agenticfabriq.com/api/v1

Authentication

All requests require app credentials in headers:

X-App-Id: your-app-id
X-App-Secret: your-app-secret

Endpoints Overview

MethodEndpointDescription
POST/apps/{app_id}/external-usersCreate or update an external user
GET/apps/{app_id}/external-users/{external_user_id}Get external user details
DELETE/apps/{app_id}/external-users/{external_user_id}Delete external user and connections
GET/apps/{app_id}/external-users/{external_user_id}/connectionsList user's tool connections
POST/apps/{app_id}/external-users/{external_user_id}/oauth/{provider}/initiateStart OAuth flow
POST/apps/{app_id}/external-users/{external_user_id}/tokenIssue MCP JWT token
DELETE/apps/{app_id}/external-users/{external_user_id}/connections/{connection_id}Delete a connection (full)
POST/apps/{app_id}/external-users/{external_user_id}/connections/{connection_id}/disconnectDisconnect (soft, keeps metadata)

Supported OAuth Providers

  • Google: gmail, google_drive, google_docs, google_sheets, google_slides, google_calendar, google_meet, google_forms, google_contacts, google_chat
  • Other: slack, github, notion

Security Considerations

Credential Storage

  • Never store app_secret in frontend code or version control
  • Use environment variables or a secrets manager
  • Rotate secrets periodically

External User IDs

  • User IDs are visible in URLs and logs - don't use sensitive data
  • Recommended: Use UUIDs or opaque IDs, not emails
  • Valid characters: a-z, A-Z, 0-9, -, _, @, .
  • Maximum length: 255 characters

Token Handling

  • MCP tokens are short-lived (default 1 hour)
  • Don't store MCP tokens long-term
  • Request new tokens for each agent session

Rate Limiting

  • OAuth initiation: 10 requests per minute per user
  • Token issuance: 60 requests per minute per app
  • MCP calls: Based on your plan limits

Troubleshooting

"B2B2C not enabled" error

Make sure you checked "Enable B2B2C Mode" when registering your app. You may need to re-register the app with B2B2C enabled.

OAuth callback not working

  1. Verify your callback URL exactly matches what you registered
  2. Check that the URL is publicly accessible
  3. Ensure it handles the query parameters (status, external_user_id, tool)

MCP token rejected

  1. Check the token hasn't expired
  2. Verify the token is for the correct app
  3. Ensure the user has connected the tool you're trying to use

Tool call fails with "no connection"

The user hasn't connected that tool yet. Prompt them to connect it first.

Need help?

Our team is here to help you get started.