npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

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

Readme

UCL Auth API Client

TypeScript SDK for Universal Context Layer (UCL) - Connect and manage third-party integrations with ease.

npm version License: MIT

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 calls

Connect 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

  1. Connect - User authorizes your app to access their service (OAuth) or provides API key
  2. Credentials - UCL securely stores and manages access tokens/refresh tokens
  3. 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):

  1. ✅ Fetches connector metadata
  2. ✅ Saves connection state to backend
  3. ✅ Initiates OAuth flow
  4. ✅ 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 timeout in waitForConnection if 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:

  1. Log in to your UCL workspace at https://live.fastn.ai
  2. Navigate to Connectors section
  3. Each connector will display its unique ID (UUID format)
  4. 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 test

Run tests with coverage:

npm test -- --coverage

Run 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:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Write tests for your changes
  4. Ensure all tests pass (npm test)
  5. Commit your changes (git commit -m 'Add amazing feature')
  6. Push to your branch (git push origin feature/amazing-feature)
  7. 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?


Made with ❤️ by the UCL team