Skip to main content

Installation

Install the SDK using Bundler or RubyGems: Using Bundler (recommended): Add this line to your application’s Gemfile:
gem 'bundleup-sdk'
And then execute:
bundle install
Using RubyGems:
gem install bundleup-sdk

Requirements

  • Ruby: 2.7.0 or higher
  • Faraday: ~> 2.0 (automatically installed as a dependency)

Ruby Compatibility

The BundleUp SDK is tested and supported on:
  • Ruby 2.7.x
  • Ruby 3.0.x
  • Ruby 3.1.x
  • Ruby 3.2.x
  • Ruby 3.3.x

Features

  • 🚀 Ruby Idiomatic - Follows Ruby best practices and conventions
  • 📦 Easy Integration - Simple, intuitive API design
  • HTTP/2 Support - Built on Faraday for modern HTTP features
  • 🔌 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 - Minimal dependencies
  • 🛡️ Error Handling - Comprehensive error messages and validation
  • 📚 Well Documented - Extensive documentation and examples
  • 🧪 Tested - Comprehensive test suite with RSpec

Quick Start

Get started with BundleUp in just a few lines of code:
require 'bundleup'

# Initialize the client
client = BundleUp::Client.new(ENV['BUNDLEUP_API_KEY'])

# List all active connections
connections = client.connections.list
puts "You have #{connections.length} active connections"

# Use the Proxy API to make requests to integrated services
proxy = client.proxy('conn_123')
response = proxy.get('/api/users')
puts "Users: #{response.body}"

# Use the Unify API for standardized data across integrations
unify = client.unify('conn_456')
channels = unify.chat.channels(limit: 10)
puts "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

require 'bundleup'

# Initialize with API key
client = BundleUp::Client.new('your_api_key_here')

# Or use environment variable (recommended)
client = BundleUp::Client.new(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 (using dotenv): Add to your Gemfile:
gem 'dotenv'
Then in your application:
require 'dotenv/load'
require 'bundleup'

client = BundleUp::Client.new(ENV['BUNDLEUP_API_KEY'])
For Rails applications:
# config/initializers/bundleup.rb
BUNDLEUP_CLIENT = BundleUp::Client.new(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.
connections = client.connections.list
With query parameters:
connections = 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 (Integer): Maximum number of results (default: 50, max: 100)
  • offset (Integer): Number of results to skip for pagination
Response:
[
  {
    'id' => 'conn_123abc',
    'external_id' => 'user_456',
    'integration_id' => 'int_slack',
    'is_valid' => true,
    'created_at' => '2024-01-15T10:30:00Z',
    'updated_at' => '2024-01-20T14:22:00Z',
    'refreshed_at' => '2024-01-20T14:22:00Z',
    'expires_at' => '2024-04-20T14:22:00Z'
  },
  # ... more connections
]

Retrieve a Connection

Get details of a specific connection by ID.
connection = client.connections.retrieve('conn_123abc')
Response:
{
  'id' => 'conn_123abc',
  'external_id' => 'user_456',
  'integration_id' => 'int_slack',
  'is_valid' => true,
  'created_at' => '2024-01-15T10:30:00Z',
  'updated_at' => '2024-01-20T14:22:00Z',
  'refreshed_at' => '2024-01-20T14:22:00Z',
  'expires_at' => '2024-04-20T14:22:00Z'
}

Delete a Connection

Remove a connection from your account.
client.connections.delete('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.
integrations = client.integrations.list
With query parameters:
integrations = client.integrations.list(
  status: 'active',
  limit: 100,
  offset: 0
)
Query Parameters:
  • status (String): Filter by status (‘active’, ‘inactive’, ‘beta’)
  • limit (Integer): Maximum number of results
  • offset (Integer): Number of results to skip for pagination
Response:
[
  {
    'id' => 'int_slack',
    'identifier' => 'slack',
    'name' => 'Slack',
    'category' => 'chat',
    'created_at' => '2023-01-01T00:00:00Z',
    'updated_at' => '2024-01-15T10:00:00Z'
  },
  # ... more integrations
]

Retrieve an Integration

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

Webhooks

Manage webhook subscriptions for real-time event notifications.

List Webhooks

Get all registered webhooks.
webhooks = client.webhooks.list
With pagination:
webhooks = 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
    },
    'created_at' => '2024-01-15T10:30:00Z',
    'updated_at' => '2024-01-20T14:22:00Z',
    'last_triggered_at' => '2024-01-20T14:22:00Z'
  }
]

Create a Webhook

Register a new webhook endpoint.
webhook = 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 (Hash): Events to subscribe to
Response:
{
  'id' => 'webhook_123',
  'name' => 'Connection Events Webhook',
  'url' => 'https://example.com/webhook',
  'events' => {
    'connection.created' => true,
    'connection.deleted' => true,
    'connection.updated' => true
  },
  'created_at' => '2024-01-15T10:30:00Z',
  'updated_at' => '2024-01-15T10:30:00Z'
}

Retrieve a Webhook

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

Update a Webhook

Modify an existing webhook.
updated = 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.
client.webhooks.delete('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 (Rails Example)

To verify webhook signatures in a Rails application:
# app/controllers/webhooks_controller.rb
class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def create
    signature = request.headers['BundleUp-Signature']
    payload = request.body.read

    unless verify_signature(payload, signature)
      render json: { error: 'Invalid signature' }, status: :unauthorized
      return
    end

    event = JSON.parse(payload)
    process_webhook_event(event)

    head :ok
  end

  private

  def verify_signature(payload, signature)
    secret = ENV['BUNDLEUP_WEBHOOK_SECRET']
    computed = OpenSSL::HMAC.hexdigest('SHA256', secret, payload)
    ActiveSupport::SecurityUtils.secure_compare(computed, signature)
  end

  def process_webhook_event(event)
    case event['type']
    when 'connection.created'
      handle_connection_created(event['data'])
    when 'connection.deleted'
      handle_connection_deleted(event['data'])
    # ... more event handlers
    end
  end
end

Proxy API

Make direct HTTP requests to integration APIs through BundleUp.

Creating a Proxy Instance

proxy = client.proxy('conn_123abc')

GET Request

response = proxy.get('/api/users')
data = response.body
puts data
With custom headers:
response = proxy.get('/api/users', headers: {
  'X-Custom-Header' => 'value',
  'Accept' => 'application/json'
})

POST Request

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

new_user = response.body
puts "Created user: #{new_user}"
With custom headers:
response = proxy.post(
  '/api/users',
  body: { name: 'John Doe' },
  headers: {
    'Content-Type' => 'application/json',
    'X-API-Version' => '2.0'
  }
)

PUT Request

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

updated_user = response.body

PATCH Request

response = proxy.patch('/api/users/123', body: {
  email: 'newemail@example.com'
})

partially_updated = response.body

DELETE Request

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

if response.success?
  puts 'User deleted successfully'
end

Working with Response Objects

The Proxy API returns Faraday response objects:
response = proxy.get('/api/users')

# Access response body
data = response.body

# Check status code
puts response.status # => 200

# Check if successful
puts response.success? # => true

# Access headers
puts response.headers['content-type']

# Handle errors
begin
  response = proxy.get('/api/invalid')
rescue Faraday::Error => e
  puts "Request failed: #{e.message}"
end

Unify API

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

Creating a Unify Instance

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.
result = unify.chat.channels(
  limit: 100,
  after: nil,
  include_raw: false
)

puts "Channels: #{result['data']}"
puts "Next cursor: #{result['metadata']['next']}"
Parameters:
  • limit (Integer, 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:
all_channels = []
cursor = nil

loop do
  result = unify.chat.channels(limit: 100, after: cursor)
  all_channels.concat(result['data'])
  cursor = result['metadata']['next']
  break if cursor.nil?
end

puts "Fetched #{all_channels.length} total channels"

Git API

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

puts "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
result = unify.git.pulls('organization/repo-name',
  limit: 20,
  after: nil,
  include_raw: false
)

puts "Pull Requests: #{result['data']}"
Parameters:
  • repo_name (String, required): Repository name in the format ‘owner/repo’
  • limit (Integer, 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' => nil
    }
  ],
  'metadata' => {
    'next' => nil
  }
}
List Tags
result = unify.git.tags('organization/repo-name', limit: 50)

puts "Tags: #{result['data']}"
Response:
{
  'data' => [
    {
      'name' => 'v1.0.0',
      'commit_sha' => 'abc123def456'
    },
    {
      'name' => 'v0.9.0',
      'commit_sha' => 'def456ghi789'
    }
  ],
  'metadata' => {
    'next' => nil
  }
}
List Releases
result = unify.git.releases('organization/repo-name', limit: 10)

puts "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' => nil
  }
}

Project Management API

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

puts "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:
open_issues = result['data'].select { |issue| issue['status'] == 'open' }
sorted_by_date = result['data'].sort_by { |issue| Time.parse(issue['created_at']) }.reverse

Error Handling

The SDK raises exceptions for errors. Always wrap SDK calls in rescue blocks for proper error handling.
begin
  connections = client.connections.list
rescue StandardError => e
  puts "Failed to fetch connections: #{e.message}"
end