Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tambo-ai/tambo/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Tambo requires authentication to identify users and scope threads. You must provide either:
userKey - A user identifier (server-side or trusted environments)
userToken - An OAuth access token (client-side with token exchange)
All thread operations (create, list, fetch) only return threads owned by the authenticated user.
Authentication Methods
userKey (Server-Side)
Use userKey when you control user identity on the server:
import { TamboProvider } from '@tambo-ai/react';
function App() {
const currentUserId = getCurrentUser().id; // Your auth system
return (
<TamboProvider
apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
userKey={currentUserId} // User identifier from your backend
>
<YourApp />
</TamboProvider>
);
}
Best for:
- Server-rendered apps (Next.js App Router)
- Trusted environments where user ID is verified server-side
- Backend-controlled authentication
userToken (Client-Side)
Use userToken with OAuth or JWT tokens:
import { TamboProvider } from '@tambo-ai/react';
import { useAuth } from './auth';
function App() {
const { accessToken } = useAuth(); // Your OAuth provider
return (
<TamboProvider
apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
userToken={accessToken} // OAuth access token
>
<YourApp />
</TamboProvider>
);
}
Best for:
- Client-side apps (SPA, React, Vite)
- OAuth providers (Auth0, Clerk, Firebase Auth)
- Token-based authentication
Tambo exchanges the token server-side and extracts the user identity.
Authentication States
Monitor auth state with useTamboAuthState():
import { useTamboAuthState } from '@tambo-ai/react';
function AuthStatus() {
const authState = useTamboAuthState();
switch (authState.status) {
case "identified":
return <div>Authenticated via {authState.source}</div>;
case "unauthenticated":
return <div>No authentication provided</div>;
case "exchanging":
return <div>Exchanging token...</div>;
case "invalid":
return <div>Both userKey and userToken provided - use only one</div>;
case "error":
return <div>Auth error: {authState.error.message}</div>;
}
}
Auth State Types
type TamboAuthState =
| { status: "identified"; source: "userKey" | "tokenExchange" }
| { status: "unauthenticated" }
| { status: "exchanging" }
| { status: "invalid" }
| { status: "error"; error: Error };
Thread Scoping
Threads are automatically scoped to the authenticated user:
function ThreadList() {
// Only returns threads for current user
const { data: threads } = useTamboThreadList();
return (
<ul>
{threads?.map((thread) => (
<li key={thread.id}>{thread.name}</li>
))}
</ul>
);
}
Important: You cannot access threads from other users. The API filters by user identity automatically.
Token Exchange Flow
When using userToken, Tambo performs a token exchange:
- Client provides OAuth access token
- Tambo validates token with OAuth provider
- Tambo extracts user identity (sub, email, etc.)
- Tambo issues internal session token
- Client uses session token for API calls
Token exchange happens automatically—no code needed.
Auth Warnings
TamboProvider emits console warnings for common auth issues:
Missing Authentication
[TamboProvider] Neither userKey nor userToken provided.
API requests will be blocked until authentication is configured.
Fix: Add userKey or userToken prop.
Invalid Configuration
[TamboProvider] Both userKey and userToken were provided.
You must provide one or the other, not both.
Fix: Remove either userKey or userToken.
Token Exchange Error
[TamboProvider] Token exchange failed: Invalid token
Fix: Verify the token is valid and not expired.
Integration Examples
Next.js App Router
Server-side user identification:
// app/layout.tsx
import { auth } from '@/lib/auth';
import { TamboProvider } from '@tambo-ai/react';
export default async function RootLayout({ children }) {
const session = await auth();
return (
<html>
<body>
<TamboProvider
apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
userKey={session?.user?.id} // Server-side user ID
>
{children}
</TamboProvider>
</body>
</html>
);
}
Clerk
OAuth token from Clerk:
import { useAuth } from '@clerk/nextjs';
import { TamboProvider } from '@tambo-ai/react';
function App() {
const { getToken } = useAuth();
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
getToken().then(setToken);
}, [getToken]);
return (
<TamboProvider
apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
userToken={token ?? undefined}
>
<YourApp />
</TamboProvider>
);
}
Auth0
OAuth token from Auth0:
import { useAuth0 } from '@auth0/auth0-react';
import { TamboProvider } from '@tambo-ai/react';
function App() {
const { getAccessTokenSilently } = useAuth0();
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
getAccessTokenSilently().then(setToken);
}, [getAccessTokenSilently]);
return (
<TamboProvider
apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
userToken={token ?? undefined}
>
<YourApp />
</TamboProvider>
);
}
Firebase Auth
OAuth token from Firebase:
import { getAuth, onAuthStateChanged } from 'firebase/auth';
import { TamboProvider } from '@tambo-ai/react';
function App() {
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
const auth = getAuth();
const unsubscribe = onAuthStateChanged(auth, async (user) => {
if (user) {
const idToken = await user.getIdToken();
setToken(idToken);
} else {
setToken(null);
}
});
return unsubscribe;
}, []);
return (
<TamboProvider
apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
userToken={token ?? undefined}
>
<YourApp />
</TamboProvider>
);
}
MCP Authentication
MCP servers receive authentication automatically:
Server-Side MCP
Tambo’s internal MCP server receives an access token automatically:
// No code needed - token is handled automatically
<TamboProvider
apiKey={apiKey}
userKey={userId} // Used to generate MCP token
/>
Client-Side MCP
Pass auth headers to MCP servers:
const mcpServers = [
{
name: "linear",
url: "https://mcp.linear.app",
transport: MCPTransport.HTTP,
customHeaders: {
Authorization: `Bearer ${linearApiKey}`,
},
},
];
<TamboProvider
apiKey={apiKey}
userKey={userId}
mcpServers={mcpServers}
/>
Security Best Practices
Never Expose API Keys
// ✅ Good - Use environment variables
apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
// ❌ Bad - Hardcoded
apiKey="tambo_pk_abc123def456"
Validate Tokens Server-Side
When using userKey, verify the user is authenticated:
// app/layout.tsx (Next.js)
const session = await auth();
if (!session?.user) {
redirect('/login');
}
return (
<TamboProvider
apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
userKey={session.user.id}
>
{children}
</TamboProvider>
);
Use Short-Lived Tokens
Refresh tokens before expiry:
function App() {
const { getToken } = useAuth();
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
// Refresh token every 50 minutes (assuming 1-hour expiry)
const interval = setInterval(() => {
getToken({ refresh: true }).then(setToken);
}, 50 * 60 * 1000);
return () => clearInterval(interval);
}, [getToken]);
// ...
}
Handle Unauthenticated State
Block UI when unauthenticated:
function ProtectedChat() {
const authState = useTamboAuthState();
if (authState.status === "unauthenticated") {
return <LoginPrompt />;
}
if (authState.status === "exchanging") {
return <Spinner />;
}
return <ChatInterface />;
}
Environment Variables
Required
# Tambo API Key (required)
NEXT_PUBLIC_TAMBO_API_KEY=tambo_pk_...
Optional
# Custom Tambo API URL (for self-hosting)
NEXT_PUBLIC_TAMBO_URL=https://api.your-domain.com
# Environment (production or staging)
NEXT_PUBLIC_TAMBO_ENVIRONMENT=production
Next Steps
- Learn about Threads and how they’re scoped to users
- Explore MCP authentication for external integrations
- See Streaming for real-time updates
- Check the API Reference for full provider options