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:
- An Agentic Fabriq organization account with admin access
- A registered application with B2B2C mode enabled
- Your app credentials:
app_idandapp_secret - A backend server that can make HTTPS requests
- A callback URL on your domain (e.g.,
https://yourapp.com/oauth/callback)
Initial Setup
Step 1: Register Your Application
- Go to the Agentic Fabriq admin console
- Navigate to Admin Console → Applications
- Click Register Application
- 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)
- Application ID: A unique identifier (e.g.,
- Select the provider scopes your app needs (e.g., Gmail, Slack)
- Complete the registration and activation process
- Save your
app_idandapp_secretsecurely
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-hereBackend Integration
Your backend handles three main responsibilities:
- Initiating OAuth flows for users
- Handling OAuth callbacks
- 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.pyConfiguration
# 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:
- Show which tools are connected
- Provide buttons to connect/disconnect tools
- 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:
- Get an MCP token for the user
- 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 resultB2B2C API Reference
Base URL
https://api.agenticfabriq.com/api/v1
# For staging/development:
https://staging.agenticfabriq.com/api/v1Authentication
All requests require app credentials in headers:
X-App-Id: your-app-id
X-App-Secret: your-app-secretEndpoints Overview
| Method | Endpoint | Description |
|---|---|---|
POST | /apps/{app_id}/external-users | Create 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}/connections | List user's tool connections |
POST | /apps/{app_id}/external-users/{external_user_id}/oauth/{provider}/initiate | Start OAuth flow |
POST | /apps/{app_id}/external-users/{external_user_id}/token | Issue 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}/disconnect | Disconnect (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_secretin 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
- Verify your callback URL exactly matches what you registered
- Check that the URL is publicly accessible
- Ensure it handles the query parameters (
status,external_user_id,tool)
MCP token rejected
- Check the token hasn't expired
- Verify the token is for the correct app
- 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.