> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bundleup.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Ruby

## Installation

Install the SDK using Bundler or RubyGems:

**Using Bundler (recommended):**

Add this line to your application's Gemfile:

```ruby theme={null}
gem 'bundleup-sdk'
```

And then execute:

```bash theme={null}
bundle install
```

**Using RubyGems:**

```bash theme={null}
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:

```ruby theme={null}
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](https://app.bundleup.io).

### Getting Your API Key

1. Sign in to your [BundleUp Dashboard](https://app.bundleup.io)
2. Navigate to **API Keys**
3. Click **Create API Key**
4. Copy your API key and store it securely

### Initializing the SDK

```ruby theme={null}
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:`**

```bash theme={null}
BUNDLEUP_API_KEY=bu_live_1234567890abcdefghijklmnopqrstuvwxyz
```

**Loading environment variables (using dotenv):**

Add to your Gemfile:

```ruby theme={null}
gem 'dotenv'
```

Then in your application:

```ruby theme={null}
require 'dotenv/load'
require 'bundleup'

client = BundleUp::Client.new(ENV['BUNDLEUP_API_KEY'])
```

**For Rails applications:**

```ruby theme={null}
# 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.

```ruby theme={null}
connections = client.connections.list
```

**With query parameters:**

```ruby theme={null}
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:**

```ruby theme={null}
[
  {
    'id' => 'conn_123abc',
    'external_id' => 'user_456',
    'integration_id' => 'int_slack',
    'created_at' => '2024-01-15T10:30:00Z',
    'updated_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.

```ruby theme={null}
connection = client.connections.retrieve('conn_123abc')
```

**Response:**

```ruby theme={null}
{
  'id' => 'conn_123abc',
  'external_id' => 'user_456',
  'integration_id' => 'int_slack',
  'created_at' => '2024-01-15T10:30:00Z',
  'updated_at' => '2024-01-20T14:22:00Z',
  'expires_at' => '2024-04-20T14:22:00Z'
}
```

#### Delete a Connection

Remove a connection from your account.

```ruby theme={null}
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.

```ruby theme={null}
integrations = client.integrations.list
```

**With query parameters:**

```ruby theme={null}
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:**

```ruby theme={null}
[
  {
    '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.

```ruby theme={null}
integration = client.integrations.retrieve('int_slack')
```

**Response:**

```ruby theme={null}
{
  '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.

```ruby theme={null}
webhooks = client.webhooks.list
```

**With pagination:**

```ruby theme={null}
webhooks = client.webhooks.list(
  limit: 50,
  offset: 0
)
```

**Response:**

```ruby theme={null}
[
  {
    '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.

```ruby theme={null}
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:**

```ruby theme={null}
{
  '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.

```ruby theme={null}
webhook = client.webhooks.retrieve('webhook_123')
```

#### Update a Webhook

Modify an existing webhook.

```ruby theme={null}
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.

```ruby theme={null}
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:

```json theme={null}
{
  "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",
    "created_at": "2024-01-15T10:30:00Z"
  }
}
```

#### Webhook Security (Rails Example)

To verify webhook signatures in a Rails application:

```ruby theme={null}
# 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

```ruby theme={null}
proxy = client.proxy('conn_123abc')
```

#### GET Request

```ruby theme={null}
response = proxy.get('/api/users')
data = response.body
puts data
```

**With custom headers:**

```ruby theme={null}
response = proxy.get('/api/users', headers: {
  'X-Custom-Header' => 'value',
  'Accept' => 'application/json'
})
```

#### POST Request

```ruby theme={null}
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:**

```ruby theme={null}
response = proxy.post(
  '/api/users',
  body: { name: 'John Doe' },
  headers: {
    'Content-Type' => 'application/json',
    'X-API-Version' => '2.0'
  }
)
```

#### PUT Request

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

updated_user = response.body
```

#### PATCH Request

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

partially_updated = response.body
```

#### DELETE Request

```ruby theme={null}
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:

```ruby theme={null}
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

```ruby theme={null}
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.

```ruby theme={null}
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:**

```ruby theme={null}
{
  '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:**

```ruby theme={null}
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

```ruby theme={null}
result = unify.git.repos(
  limit: 50,
  after: nil,
  include_raw: false
)

puts "Repositories: #{result['data']}"
```

**Response:**

```ruby theme={null}
{
  '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

```ruby theme={null}
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:**

```ruby theme={null}
{
  '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

```ruby theme={null}
result = unify.git.tags('organization/repo-name', limit: 50)

puts "Tags: #{result['data']}"
```

**Response:**

```ruby theme={null}
{
  'data' => [
    {
      'name' => 'v1.0.0',
      'commit_sha' => 'abc123def456'
    },
    {
      'name' => 'v0.9.0',
      'commit_sha' => 'def456ghi789'
    }
  ],
  'metadata' => {
    'next' => nil
  }
}
```

##### List Releases

```ruby theme={null}
result = unify.git.releases('organization/repo-name', limit: 10)

puts "Releases: #{result['data']}"
```

**Response:**

```ruby theme={null}
{
  '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

```ruby theme={null}
result = unify.pm.issues(
  limit: 100,
  after: nil,
  include_raw: false
)

puts "Issues: #{result['data']}"
```

**Response:**

```ruby theme={null}
{
  '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:**

```ruby theme={null}
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.

```ruby theme={null}
begin
  connections = client.connections.list
rescue StandardError => e
  puts "Failed to fetch connections: #{e.message}"
end
```
