ucl-auth-api-experimental
v0.2.0
Published
TypeScript SDK for Universal Context Layer - Connect and manage 100+ third-party integrations (Gmail, Slack, Shopify, OpenAI) with OAuth and API key support
Maintainers
Readme
UCL Auth API Client
TypeScript SDK for Universal Context Layer (UCL) - Connect and manage third-party integrations with ease.
Connect your users to 100+ services (Gmail, Slack, Shopify, OpenAI, etc.) with just a few lines of code. Works in Node.js and browsers.
📦 Installation
npm install ucl-auth-api
# or
yarn add ucl-auth-api🚀 Quick Start
Initialize the Client
Option 1: Static Token (if your token doesn't expire)
import { UclClient } from 'ucl-auth-api';
const ucl = new UclClient({
workspaceId: 'your-workspace-id',
authToken: 'your-auth-token',
});Option 2: Dynamic Token (recommended for expiring tokens)
import { UclClient } from 'ucl-auth-api';
const ucl = new UclClient({
workspaceId: 'your-workspace-id',
authToken: async () => {
// Fetch fresh token from your backend
const response = await fetch('/api/get-ucl-token');
const data = await response.json();
return data.token;
},
});Option 3: Update Token Later
const ucl = new UclClient({
workspaceId: 'your-workspace-id',
authToken: 'initial-token',
});
// Update token when it expires
ucl.setAuthToken(async () => {
const newToken = await getRefreshedToken();
return newToken;
});Connect an OAuth Service (Gmail, Slack, Shopify...)
Note: Connector IDs are unique identifiers (UUIDs) from your UCL workspace, not service names.
// Step 1: Initiate connection
const result = await ucl.connectors.connect({
connectorId: '5743b46c-38a0-418f-84fa-8ed18198f72e', // Gmail connector ID
tenantId: 'user-123',
});
// Step 2: Redirect user to authorize
if (result.type === 'OAUTH') {
window.location.href = result.redirectUrl;
}
// Step 3: After redirect, wait for connection to complete
const connection = await ucl.connectors.waitForConnection({
connectorId: '5743b46c-38a0-418f-84fa-8ed18198f72e',
tenantId: 'user-123',
});
// Step 4: Get access credentials
const auth = await ucl.connectors.auth({
connectorId: '5743b46c-38a0-418f-84fa-8ed18198f72e',
tenantId: 'user-123',
});
console.log(auth.auth.access_token); // Use this to make API callsConnect an API Key Service (OpenAI, Stripe...)
// Step 1: Connect with API key
const result = await ucl.connectors.connect({
connectorId: 'a8f3c9e1-2b4d-4f6a-9c2e-1d3f5e7a9b2c', // OpenAI connector ID
tenantId: 'user-123',
config: {
access_token: 'sk-...',
},
});
// Step 2: Get credentials
if (result.type === 'DIRECT' && result.status === 'SUCCESS') {
const auth = await ucl.connectors.auth({
connectorId: 'a8f3c9e1-2b4d-4f6a-9c2e-1d3f5e7a9b2c',
tenantId: 'user-123',
});
console.log(auth.auth.access_token);
}📚 Core Concepts
How It Works
- Connect - User authorizes your app to access their service (OAuth) or provides API key
- Credentials - UCL securely stores and manages access tokens/refresh tokens
- Retrieve - Your app requests credentials when needed to make API calls
Two Types of Connectors
| Type | Examples | Flow | |------|----------|------| | OAuth | Gmail, Slack, Shopify, GitHub | User redirects to service → authorizes → redirects back | | API Key | OpenAI, Stripe (in some cases) | User provides API key directly |
🎯 Common Use Cases
Use Case 1: Building an Integration Page
// Get connector ID from your UCL workspace
const gmailConnectorId = '5743b46c-38a0-418f-84fa-8ed18198f72e';
// Display available connectors
const metadata = await ucl.connectors.getMetadata({
connectorId: gmailConnectorId,
tenantId: 'user-123',
});
if (metadata.isOauth) {
// Show "Connect with Gmail" button
console.log(`Connect with ${metadata.name}`);
} else {
// Show form to enter API key
console.log(`Enter API key for ${metadata.name}`);
}Use Case 2: Making API Calls on Behalf of User
// Get fresh credentials for a connected service
const { auth } = await ucl.connectors.auth({
connectorId: '5743b46c-38a0-418f-84fa-8ed18198f72e', // Gmail connector ID
tenantId: 'user-123',
refresh: true, // Auto-refresh if expired
});
// Use the token to make API calls
const response = await fetch('https://gmail.googleapis.com/gmail/v1/users/me/messages', {
headers: {
Authorization: `Bearer ${auth.access_token}`,
},
});Use Case 3: Handling OAuth Callback
// After user authorizes and returns to your app
const connection = await ucl.connectors.waitForConnection({
connectorId: '5743b46c-38a0-418f-84fa-8ed18198f72e', // Gmail connector ID
tenantId: 'user-123',
timeout: 120000, // 2 minutes
pollInterval: 2000, // Check every 2 seconds
});
if (connection.status === 'ACTIVE') {
// Connection successful! Show success message
console.log(`Connected to ${connection.metadata.name}`);
} else if (connection.status === 'TIMEOUT') {
// User took too long or closed the window
console.error('Connection timed out');
}Use Case 4: Managing Expiring Auth Tokens
// Best practice: Use dynamic token provider
const ucl = new UclClient({
workspaceId: 'your-workspace-id',
authToken: async () => {
// Always fetch the latest token from your backend
const response = await fetch('/api/auth/ucl-token', {
headers: {
Authorization: `Bearer ${getUserSessionToken()}`,
},
});
if (!response.ok) {
throw new Error('Failed to get UCL token');
}
const data = await response.json();
return data.uclToken;
},
});
// The SDK will automatically call your function before each API request
// No need to manually refresh!📖 API Reference
new UclClient(config)
Create a new UCL client instance.
const ucl = new UclClient({
workspaceId: string, // Required: Your UCL workspace ID
authToken?: string | Function, // Optional: Auth token or async provider
baseUrl?: string, // Optional: Default is live.fastn.ai
stage?: string, // Optional: Default is 'LIVE'
fetch?: typeof fetch, // Optional: Custom fetch implementation
});Token Management Options:
Static Token (simple, but requires manual updates):
const ucl = new UclClient({
workspaceId: 'b727dcf8-dc92-4ecc-8fa4-36109c7eb9ea',
authToken: 'static-token-here',
});
// Update manually when token expires
ucl.setAuthToken('new-token-here');Dynamic Token Provider (recommended - auto-refreshes):
const ucl = new UclClient({
workspaceId: 'b727dcf8-dc92-4ecc-8fa4-36109c7eb9ea',
authToken: async () => {
// SDK calls this function before each request
// Always returns fresh token from your backend
const response = await fetch('/api/ucl-token');
const data = await response.json();
return data.token;
},
});
// No manual token management needed!Update Token at Runtime:
const ucl = new UclClient({
workspaceId: 'b727dcf8-dc92-4ecc-8fa4-36109c7eb9ea',
authToken: 'initial-token',
});
// Later, update to dynamic provider
ucl.setAuthToken(async () => {
return await getLatestUclToken();
});connectors.connect(params)
Initiate a new connector connection.
Important: Use the actual connector ID (UUID) from your UCL workspace, not the service name.
Parameters:
{
connectorId: string, // Required: UUID from UCL workspace (e.g., '5743b46c-38a0-418f-84fa-8ed18198f72e')
tenantId?: string, // Optional: User/tenant identifier
orgId?: string, // Optional: Organization ID (default: 'community')
config?: { // Optional: Connector-specific config
subdomain?: string, // For services like Shopify
access_token?: string, // For API key connectors
// ... other connector-specific fields
},
}Returns:
// For OAuth connectors
{
type: 'OAUTH',
redirectUrl: string,
connectorId: string,
}
// For API Key connectors
{
type: 'DIRECT',
status: 'SUCCESS' | 'FAILED',
connectorId: string,
error?: string,
}What happens behind the scenes (OAuth):
- ✅ Fetches connector metadata
- ✅ Saves connection state to backend
- ✅ Initiates OAuth flow
- ✅ Returns redirect URL with state parameter
Example:
// Gmail (OAuth) - use your actual connector ID
const result = await ucl.connectors.connect({
connectorId: '5743b46c-38a0-418f-84fa-8ed18198f72e',
tenantId: 'user-123',
});
if (result.type === 'OAUTH') {
// Redirect to OAuth provider
window.location.href = result.redirectUrl;
}connectors.auth(params)
Get access credentials for an already-connected connector.
Parameters:
{
connectorId: string,
tenantId?: string,
orgId?: string,
refresh?: boolean, // Auto-refresh expired tokens
}Returns:
{
auth: {
access_token: string,
expires_in?: number,
token_type?: string,
refresh_token?: string,
// ... other service-specific fields
}
}connectors.waitForConnection(params)
Poll for connector activation after OAuth redirect.
Parameters:
{
connectorId: string,
tenantId?: string,
timeout?: number, // Default: 60000ms (1 min)
pollInterval?: number, // Default: 2000ms (2 sec)
}Returns:
{
status: 'ACTIVE' | 'TIMEOUT' | 'FAILED',
metadata?: ConnectorMetadata,
error?: string,
}When to use:
- After redirecting user for OAuth authorization
- To check if connection completed successfully
- To detect if user canceled or timed out
connectors.getMetadata(params)
Fetch connector information before connecting.
Parameters:
{
connectorId: string,
tenantId?: string,
}Returns:
{
id: string,
name: string,
isOauth: boolean,
activateStatus: 'ACTIVE' | 'INACTIVE',
oauth?: {
baseUrl: string,
clientId: string,
params: { scope: string, ... },
},
oauthInputContract?: {
api_key: {
type: 'password',
description: 'Your API Key',
},
// ... other fields
},
}When to use:
- To determine if connector is OAuth or API Key
- To display required fields in your UI
- To check if connector is already connected
🔧 Advanced Usage
Custom Fetch Implementation
import nodeFetch from 'node-fetch';
const ucl = new UclClient({
workspaceId: 'ws_123',
authToken: 'token',
fetch: nodeFetch as any,
});Handling Different Connector Types
async function connectService(connectorId: string, userId: string) {
// Get connector info first
const metadata = await ucl.connectors.getMetadata({
connectorId,
tenantId: userId,
});
if (metadata.activateStatus === 'ACTIVE') {
return { alreadyConnected: true };
}
if (metadata.isOauth) {
// OAuth flow
const result = await ucl.connectors.connect({
connectorId,
tenantId: userId,
});
return { redirectUrl: result.redirectUrl };
} else {
// Show form to collect API key
return { showApiKeyForm: true, fields: metadata.oauthInputContract };
}
}Error Handling
try {
const auth = await ucl.connectors.auth({
connectorId: '5743b46c-38a0-418f-84fa-8ed18198f72e', // Your connector ID
tenantId: 'user-123',
});
} catch (error) {
if (error.message.includes('HTTP 401')) {
// Token expired or invalid
console.error('Authentication failed. Reconnect required.');
} else if (error.message.includes('HTTP 404')) {
// Connector not found or not connected
console.error('Connector not connected.');
} else {
console.error('Unexpected error:', error);
}
}🐛 Troubleshooting
"authToken is required" error
Problem: You didn't provide an auth token when creating the client.
Solution:
const ucl = new UclClient({
workspaceId: 'ws_123',
authToken: 'your-token-here', // ✅ Add this
});OAuth redirect not working
Problem: User is redirected but connection doesn't complete.
Checklist:
- ✅ Ensure redirect URI is configured in your UCL workspace
- ✅ Check if user completed authorization (didn't close window)
- ✅ Increase
timeoutinwaitForConnectionif needed - ✅ Check browser console for errors
Credentials are expired
Problem: Access token no longer works.
Solution:
const { auth } = await ucl.connectors.auth({
connectorId: '5743b46c-38a0-418f-84fa-8ed18198f72e', // Your connector ID
tenantId: 'user-123',
refresh: true, // ✅ Auto-refresh the token
});"How do I find my connector ID?"
Answer: Connector IDs are UUIDs that you get from your UCL workspace dashboard.
Steps:
- Log in to your UCL workspace at https://live.fastn.ai
- Navigate to Connectors section
- Each connector will display its unique ID (UUID format)
- Copy and use that ID in your code
Example connector IDs:
- Gmail:
5743b46c-38a0-418f-84fa-8ed18198f72e - Slack:
8f2a4c6e-1b3d-4f6a-9c2e-5d7f8e9a1b3c - Shopify:
c9e1f3a5-7b9d-4f6c-8e2a-1d3f5e7a9b2c
These are examples - your actual IDs will be different!
🧪 Testing
Run unit tests:
npm testRun tests with coverage:
npm test -- --coverageRun E2E tests with real API:
export UCL_WORKSPACE_ID=<your-workspace-id>
export UCL_AUTH_TOKEN=<your-token>
export UCL_CONNECTOR_ID=<connector-id>
export UCL_TENANT_ID=<tenant-id>
export UCL_E2E=1
npm run test:e2e📦 Package Info
What's Included
- ✅ Full TypeScript support with types
- ✅ Works in Node.js and browsers
- ✅ Automatic token refresh support
- ✅ Automatic OAuth state management
- ✅ Built-in error handling
- ✅ Zero dependencies (except cross-fetch)
Browser Support
- Chrome/Edge: Latest 2 versions
- Firefox: Latest 2 versions
- Safari: Latest 2 versions
Node.js Support
- Node.js 12+
🤝 Contributing
We welcome contributions! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure all tests pass (
npm test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to your branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
git clone https://github.com/fastnai/ucl-auth-api-node.git
cd ucl-auth-api-node
npm install
npm run build
npm test📄 License
MIT © Universal Context Layer
🔗 Links
💡 Need Help?
- 📧 Email: [email protected]
- 💬 GitHub Issues: Create an issue
- 📖 Documentation: UCL Docs
Made with ❤️ by the UCL team
