Skip to main content

Installation

Install the SDK using your preferred package manager: npm:
npm install @bundleup/sdk
yarn:
yarn add @bundleup/sdk
pnpm:
pnpm add @bundleup/sdk

Requirements

  • Node.js: 16.0.0 or higher
  • TypeScript: 5.0+ (for TypeScript projects)
  • Fetch API: Native fetch support (Node.js 18+) or polyfill for older versions

Node.js Compatibility

For Node.js versions below 18, you’ll need to install a fetch polyfill:
npm install node-fetch
Then import it before using the SDK:
import fetch from 'node-fetch';

globalThis.fetch = fetch;

Features

  • 🚀 TypeScript First - Built with TypeScript, includes comprehensive type definitions
  • 📦 Modern JavaScript - ESM and CommonJS support for maximum compatibility
  • Promise-based API - Async/await support using native fetch
  • 🔌 100+ Integrations - Connect to Slack, GitHub, Jira, Linear, and many more
  • 🎯 Unified API - Consistent interface across all integrations via Unify API
  • 🔑 Proxy API - Direct access to underlying integration APIs
  • 🪶 Lightweight - Zero dependencies beyond native fetch API
  • 🛡️ Error Handling - Comprehensive error messages and validation
  • 📚 Well Documented - Extensive documentation and examples

Quick Start

Get started with BundleUp in just a few lines of code:
import { BundleUp } from '@bundleup/sdk';

// Initialize the client
const client = new BundleUp(process.env.BUNDLEUP_API_KEY);

// List all active connections
const connections = await client.connections.list();
console.log(`You have ${connections.length} active connections`);

// Use the Proxy API to make requests to integrated services
const proxy = client.proxy('conn_123');
const response = await proxy.get('/api/users');
const users = await response.json();
console.log('Users:', users);

// Use the Unify API for standardized data across integrations
const unify = client.unify('conn_456');
const channels = await unify.chat.channels({ limit: 10 });
console.log('Chat channels:', channels.data);

Authentication

The BundleUp SDK uses API keys for authentication. You can obtain your API key from the BundleUp Dashboard.

Getting Your API Key

  1. Sign in to your BundleUp Dashboard
  2. Navigate to API Keys
  3. Click Create API Key
  4. Copy your API key and store it securely

Initializing the SDK

import { BundleUp } from '@bundleup/sdk';

// Initialize with API key
const client = new BundleUp('your_api_key_here');

// Or use environment variable (recommended)
const client = new BundleUp(process.env.BUNDLEUP_API_KEY);

Security Best Practices

  • DO store API keys in environment variables
  • DO use a secrets management service in production
  • DO rotate API keys regularly
  • DON’T commit API keys to version control
  • DON’T hardcode API keys in your source code
  • DON’T share API keys in public channels
Example .env file:
BUNDLEUP_API_KEY=bu_live_1234567890abcdefghijklmnopqrstuvwxyz
Loading environment variables:
// For Node.js projects
import { BundleUp } from '@bundleup/sdk';
import 'dotenv/config';

const client = new BundleUp(process.env.BUNDLEUP_API_KEY);

Core Concepts

Platform API

The Platform API provides access to core BundleUp features like managing connections and integrations. Use this API to list, retrieve, and delete connections, as well as discover available integrations.

Proxy API

The Proxy API allows you to make direct HTTP requests to the underlying integration’s API through BundleUp. This is useful when you need access to integration-specific features not covered by the Unify API.

Unify API

The Unify API provides a standardized, normalized interface across different integrations. For example, you can fetch chat channels from Slack, Discord, or Microsoft Teams using the same API call.

API Reference

Connections

Manage your integration connections.

List Connections

Retrieve a list of all connections in your account.
const connections = await client.connections.list();
With query parameters:
const connections = await client.connections.list({
  integration_id: 'int_slack',
  limit: 50,
  offset: 0,
  external_id: 'user_123',
});
Query Parameters:
  • integration_id (string): Filter by integration ID
  • integration_identifier (string): Filter by integration identifier (e.g., ‘slack’, ‘github’)
  • external_id (string): Filter by external user/account ID
  • limit (number): Maximum number of results (default: 50, max: 100)
  • offset (number): Number of results to skip for pagination
Response:
[
  {
    id: 'conn_123abc',
    externalId: 'user_456',
    integrationId: 'int_slack',
    isValid: true,
    createdAt: '2024-01-15T10:30:00Z',
    updatedAt: '2024-01-20T14:22:00Z',
    refreshedAt: '2024-01-20T14:22:00Z',
    expiresAt: '2024-04-20T14:22:00Z',
  },
  // ... more connections
];

Retrieve a Connection

Get details of a specific connection by ID.
const connection = await client.connections.retrieve('conn_123abc');
Response:
{
  id: 'conn_123abc',
  externalId: 'user_456',
  integrationId: 'int_slack',
  isValid: true,
  createdAt: '2024-01-15T10:30:00Z',
  updatedAt: '2024-01-20T14:22:00Z',
  refreshedAt: '2024-01-20T14:22:00Z',
  expiresAt: '2024-04-20T14:22:00Z'
}

Delete a Connection

Remove a connection from your account.
await client.connections.del('conn_123abc');
Note: Deleting a connection will revoke access to the integration and cannot be undone.

Integrations

Discover and work with available integrations.

List Integrations

Get a list of all available integrations.
const integrations = await client.integrations.list();
With query parameters:
const integrations = await client.integrations.list({
  status: 'active',
  limit: 100,
  offset: 0,
});
Query Parameters:
  • status (string): Filter by status (‘active’, ‘inactive’, ‘beta’)
  • limit (number): Maximum number of results
  • offset (number): Number of results to skip for pagination
Response:
[
  {
    id: 'int_slack',
    identifier: 'slack',
    name: 'Slack',
    category: 'chat',
    createdAt: '2023-01-01T00:00:00Z',
    updatedAt: '2024-01-15T10:00:00Z',
  },
  // ... more integrations
];

Retrieve an Integration

Get details of a specific integration.
const integration = await client.integrations.retrieve('int_slack');
Response:
{
  id: 'int_slack',
  identifier: 'slack',
  name: 'Slack',
  category: 'chat',
  createdAt: '2023-01-01T00:00:00Z',
  updatedAt: '2024-01-15T10:00:00Z'
}

Webhooks

Manage webhook subscriptions for real-time event notifications.

List Webhooks

Get all registered webhooks.
const webhooks = await client.webhooks.list();
With pagination:
const webhooks = await client.webhooks.list({
  limit: 50,
  offset: 0,
});
Response:
[
  {
    id: 'webhook_123',
    name: 'My Webhook',
    url: 'https://example.com/webhook',
    events: {
      'connection.created': true,
      'connection.deleted': true,
    },
    createdAt: '2024-01-15T10:30:00Z',
    updatedAt: '2024-01-20T14:22:00Z',
    lastTriggeredAt: '2024-01-20T14:22:00Z',
  },
];

Create a Webhook

Register a new webhook endpoint.
const webhook = await client.webhooks.create({
  name: 'Connection Events Webhook',
  url: 'https://example.com/webhook',
  events: {
    'connection.created': true,
    'connection.deleted': true,
    'connection.updated': true,
  },
});
Webhook Events:
  • connection.created - Triggered when a new connection is established
  • connection.deleted - Triggered when a connection is removed
  • connection.updated - Triggered when a connection is modified
Request Body:
{
  name: string;           // Friendly name for the webhook
  url: string;            // Your webhook endpoint URL
  events: {               // Events to subscribe to
    [eventName: string]: boolean;
  };
}
Response:
{
  id: 'webhook_123',
  name: 'Connection Events Webhook',
  url: 'https://example.com/webhook',
  events: {
    'connection.created': true,
    'connection.deleted': true,
    'connection.updated': true
  },
  createdAt: '2024-01-15T10:30:00Z',
  updatedAt: '2024-01-15T10:30:00Z'
}

Retrieve a Webhook

Get details of a specific webhook.
const webhook = await client.webhooks.retrieve('webhook_123');

Update a Webhook

Modify an existing webhook.
const updated = await client.webhooks.update('webhook_123', {
  name: 'Updated Webhook Name',
  url: 'https://example.com/new-webhook',
  events: {
    'connection.created': true,
    'connection.deleted': false,
  },
});

Delete a Webhook

Remove a webhook subscription.
await client.webhooks.del('webhook_123');

Webhook Payload Example

When an event occurs, BundleUp sends a POST request to your webhook URL with the following payload:
{
  "id": "evt_1234567890",
  "type": "connection.created",
  "created_at": "2024-01-15T10:30:00Z",
  "data": {
    "id": "conn_123abc",
    "external_id": "user_456",
    "integration_id": "int_slack",
    "is_valid": true,
    "created_at": "2024-01-15T10:30:00Z"
  }
}

Webhook Security

To verify webhook signatures:
import crypto from 'crypto';

function verifyWebhookSignature(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(payload);
  const digest = hmac.digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
}

// In your webhook handler
app.post('/webhook', (req, res) => {
  const signature = req.headers['bundleup-signature'];
  const payload = JSON.stringify(req.body);

  if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  // Process the webhook
  console.log('Webhook received:', req.body);
  res.status(200).send('OK');
});

Proxy API

Make direct HTTP requests to integration APIs through BundleUp.

Creating a Proxy Instance

const proxy = client.proxy('conn_123abc');

GET Request

const response = await proxy.get('/api/users');
const data = await response.json();
console.log(data);
With custom headers:
const response = await proxy.get('/api/users', {
  'X-Custom-Header': 'value',
  Accept: 'application/json',
});

POST Request

const response = await proxy.post(
  '/api/users',
  JSON.stringify({
    name: 'John Doe',
    email: 'john@example.com',
    role: 'developer',
  }),
);

const newUser = await response.json();
console.log('Created user:', newUser);
With custom headers:
const response = await proxy.post(
  '/api/users',
  JSON.stringify({ name: 'John Doe' }),
  {
    'Content-Type': 'application/json',
    'X-API-Version': '2.0',
  },
);

PUT Request

const response = await proxy.put(
  '/api/users/123',
  JSON.stringify({
    name: 'Jane Doe',
    email: 'jane@example.com',
  }),
);

const updatedUser = await response.json();

PATCH Request

const response = await proxy.patch(
  '/api/users/123',
  JSON.stringify({
    email: 'newemail@example.com',
  }),
);

const partiallyUpdated = await response.json();

DELETE Request

const response = await proxy.delete('/api/users/123');

if (response.ok) {
  console.log('User deleted successfully');
}

Working with Different Content Types

Sending form data:
const formData = new URLSearchParams();
formData.append('name', 'John Doe');
formData.append('email', 'john@example.com');

const response = await proxy.post('/api/users', formData.toString(), {
  'Content-Type': 'application/x-www-form-urlencoded',
});
Uploading files:
import FormData from 'form-data';
import fs from 'fs';

const form = new FormData();
form.append('file', fs.createReadStream('document.pdf'));
form.append('title', 'My Document');

const response = await proxy.post('/api/documents', form, form.getHeaders());

Handling Binary Data

const response = await proxy.get('/api/files/download/123');
const buffer = await response.arrayBuffer();
fs.writeFileSync('downloaded-file.pdf', Buffer.from(buffer));

Unify API

Access unified, normalized data across different integrations with a consistent interface.

Creating a Unify Instance

const unify = client.unify('conn_123abc');

Chat API

The Chat API provides a unified interface for chat platforms like Slack, Discord, and Microsoft Teams.
List Channels
Retrieve a list of channels from the connected chat platform.
const result = await unify.chat.channels({
  limit: 100,
  after: null,
  include_raw: false,
});

console.log('Channels:', result.data);
console.log('Next cursor:', result.metadata.next);
Parameters:
  • limit (number, optional): Maximum number of channels to return (default: 100, max: 1000)
  • after (string, optional): Pagination cursor from previous response
  • include_raw (boolean, optional): Include raw API response from the integration (default: false)
Response:
{
  data: [
    {
      id: 'C1234567890',
      name: 'general'
    },
    {
      id: 'C0987654321',
      name: 'engineering'
    }
  ],
  metadata: {
    next: 'cursor_abc123'  // Use this for pagination
  },
  _raw?: {  // Only present if include_raw: true
    // Original response from the integration API
  }
}
Pagination example:
let allChannels = [];
let cursor = null;

do {
  const result = await unify.chat.channels({
    limit: 100,
    after: cursor,
  });

  allChannels = [...allChannels, ...result.data];
  cursor = result.metadata.next;
} while (cursor);

console.log(`Fetched ${allChannels.length} total channels`);

Git API

The Git API provides a unified interface for version control platforms like GitHub, GitLab, and Bitbucket.
List Repositories
const result = await unify.git.repos({
  limit: 50,
  after: null,
  include_raw: false,
});

console.log('Repositories:', result.data);
Response:
{
  data: [
    {
      id: '123456',
      name: 'my-awesome-project',
      full_name: 'organization/my-awesome-project',
      description: 'An awesome project',
      url: 'https://github.com/organization/my-awesome-project',
      created_at: '2023-01-15T10:30:00Z',
      updated_at: '2024-01-20T14:22:00Z',
      pushed_at: '2024-01-20T14:22:00Z'
    }
  ],
  metadata: {
    next: 'cursor_xyz789'
  }
}
List Pull Requests
const result = await unify.git.pulls('organization/repo-name', {
  limit: 20,
  after: null,
  include_raw: false,
});

console.log('Pull Requests:', result.data);
Parameters:
  • repoName (string, required): Repository name in the format ‘owner/repo’
  • limit (number, optional): Maximum number of PRs to return
  • after (string, optional): Pagination cursor
  • include_raw (boolean, optional): Include raw API response
Response:
{
  data: [
    {
      id: '12345',
      number: 42,
      title: 'Add new feature',
      description: 'This PR adds an awesome new feature',
      draft: false,
      state: 'open',
      url: 'https://github.com/org/repo/pull/42',
      user: 'john-doe',
      created_at: '2024-01-15T10:30:00Z',
      updated_at: '2024-01-20T14:22:00Z',
      merged_at: null
    }
  ],
  metadata: {
    next: null
  }
}
List Tags
const result = await unify.git.tags('organization/repo-name', {
  limit: 50,
});

console.log('Tags:', result.data);
Response:
{
  data: [
    {
      name: 'v1.0.0',
      commit_sha: 'abc123def456'
    },
    {
      name: 'v0.9.0',
      commit_sha: 'def456ghi789'
    }
  ],
  metadata: {
    next: null
  }
}
List Releases
const result = await unify.git.releases('organization/repo-name', {
  limit: 10,
});

console.log('Releases:', result.data);
Response:
{
  data: [
    {
      id: '54321',
      name: 'Version 1.0.0',
      tag_name: 'v1.0.0',
      description: 'Initial release with all the features',
      prerelease: false,
      url: 'https://github.com/org/repo/releases/tag/v1.0.0',
      created_at: '2024-01-15T10:30:00Z',
      released_at: '2024-01-15T10:30:00Z'
    }
  ],
  metadata: {
    next: null
  }
}

Project Management API

The PM API provides a unified interface for project management platforms like Jira, Linear, and Asana.
List Issues
const result = await unify.pm.issues({
  limit: 100,
  after: null,
  include_raw: false,
});

console.log('Issues:', result.data);
Response:
{
  data: [
    {
      id: 'PROJ-123',
      url: 'https://jira.example.com/browse/PROJ-123',
      title: 'Fix login bug',
      status: 'in_progress',
      description: 'Users are unable to log in',
      created_at: '2024-01-15T10:30:00Z',
      updated_at: '2024-01-20T14:22:00Z'
    }
  ],
  metadata: {
    next: 'cursor_def456'
  }
}
Filtering and sorting:
const openIssues = result.data.filter((issue) => issue.status === 'open');
const sortedByDate = result.data.sort(
  (a, b) => new Date(b.created_at) - new Date(a.created_at),
);

Error Handling

The SDK throws standard JavaScript errors with descriptive messages. Always wrap SDK calls in try-catch blocks for proper error handling.
try {
  const connections = await client.connections.list();
} catch (error) {
  console.error('Failed to fetch connections:', error.message);
}