Basecamp Ruby SDK

April 23, 2026 ยท View on GitHub

Official Ruby SDK for the Basecamp API.

Requirements

  • Ruby 3.2+
  • Faraday HTTP client

Installation

Add to your Gemfile:

gem "basecamp-sdk"

Or install directly:

gem install basecamp-sdk

Quick Start

require "basecamp"

# Create client with access token
client = Basecamp.client(access_token: ENV["BASECAMP_TOKEN"])

# Scope to an account
account = client.for_account(ENV["BASECAMP_ACCOUNT_ID"])

# List projects
account.projects.list.each do |project|
  puts "#{project['id']}: #{project['name']}"
end

# Get a specific project
project = account.projects.get(project_id: 12345)

# Create a todo
todo = account.todos.create(
  project_id: 12345,
  todolist_id: 67890,
  content: "Review PR",
  due_on: "2024-12-31"
)

Configuration

Basic Configuration

config = Basecamp::Config.new(
  base_url: "https://3.basecampapi.com",  # Default
  timeout: 30,                             # Request timeout in seconds
  max_retries: 3,                          # Max retry attempts for GET requests
  base_delay: 1.0,                         # Base delay for exponential backoff
  max_pages: 10_000                         # Max pages for pagination
)

token_provider = Basecamp::StaticTokenProvider.new(ENV["BASECAMP_TOKEN"])
client = Basecamp::Client.new(config: config, token_provider: token_provider)

Configuration Options

OptionDefaultDescription
base_urlhttps://3.basecampapi.comBasecamp API base URL
timeout30HTTP request timeout (seconds)
max_retries3Maximum retry attempts for GET requests
base_delay1.0Base delay for exponential backoff (seconds)
max_jitter0.1Maximum random jitter added to delays
max_pages10_000Maximum pages to fetch during pagination

OAuth Authentication

Token Providers

The SDK supports multiple authentication patterns:

# Static token (simplest)
token_provider = Basecamp::StaticTokenProvider.new("your-access-token")

# OAuth with refresh
token_provider = Basecamp::OauthTokenProvider.new(
  access_token: "access-token",
  refresh_token: "refresh-token",
  expires_at: Time.now + 3600,
  client_id: ENV["BASECAMP_CLIENT_ID"],
  client_secret: ENV["BASECAMP_CLIENT_SECRET"]
)

OAuth Flow Helpers

# 1. Discover OAuth configuration
config = Basecamp::Oauth.discover_launchpad

# 2. Build authorization URL (redirect user here)
auth_url = "#{config.authorization_endpoint}?" + URI.encode_www_form(
  type: "web_server",
  client_id: ENV["BASECAMP_CLIENT_ID"],
  redirect_uri: "https://myapp.com/callback"
)

# 3. Exchange code for tokens (in callback handler)
token = Basecamp::Oauth.exchange_code(
  token_endpoint: config.token_endpoint,
  code: params[:code],
  redirect_uri: "https://myapp.com/callback",
  client_id: ENV["BASECAMP_CLIENT_ID"],
  client_secret: ENV["BASECAMP_CLIENT_SECRET"],
  use_legacy_format: true  # Required for Launchpad
)

# 4. Use the token
client = Basecamp.client(access_token: token.access_token)

# 5. Refresh when needed
if token.expired?
  token = Basecamp::Oauth.refresh_token(
    token_endpoint: config.token_endpoint,
    refresh_token: token.refresh_token,
    use_legacy_format: true
  )
end

Services

The SDK provides 37 services covering the complete Basecamp API:

ServiceDescription
projectsProject management
todosTodo items
todolistsTodo lists
todosetsTodo set containers
todolist_groupsTodolist grouping/folders
peoplePeople/users
commentsComments on recordings
messagesMessage posts
message_boardsMessage boards
message_typesMessage categories
campfiresChat rooms
schedulesCalendar schedules
documentsDocuments
vaultsFile folders
uploadsFile uploads
attachmentsBinary attachments
recordingsGeneric recordings
webhooksWebhook subscriptions
subscriptionsNotification subscriptions
templatesProject templates
eventsActivity events
checkinsAutomatic check-ins
forwardsEmail forwards
cardsCard table cards
card_tablesCard tables (kanban)
card_columnsCard table columns
card_stepsCard workflow steps
lineupCard lineup view
toolsProject dock tools
searchFull-text search
reportsActivity reports
timelineActivity timeline
timesheetTime tracking reports
client_approvalsClient approval workflows
client_correspondencesClient communications
client_repliesClient replies
authorizationAuth info

Pagination

All list methods return lazy Enumerators that automatically handle pagination:

# Automatically fetches all pages
account.projects.list.each do |project|
  puts project["name"]
end

# Take only what you need
first_10 = account.todos.list(todolist_id: 456).take(10)

# Convert to array (fetches all pages)
all_projects = account.projects.list.to_a

Downloading Files

Fetch an upload's file content in one call. The SDK fetches the upload metadata, then follows the authenticated-hop + 302 flow against the signed storage URL.

result = account.uploads.download(upload_id: 1069479400)
File.binwrite("uploaded.bin", result.body)
# result.content_type, result.content_length, result.filename are also available

For any authenticated download URL (e.g. a download_url you already have in hand), use AccountClient#download_url:

result = account.download_url(url)

Retry Behavior

GET requests automatically retry on transient failures with exponential backoff:

  • Retryable errors: 429 (rate limit), 502, 503, 504 (gateway errors)
  • Backoff: Exponential with jitter (1s, 2s, 4s...)
  • Rate limits: Respects Retry-After header

Mutation operations (POST, PUT, DELETE) do not retry to prevent data duplication.

Error Handling

begin
  account.projects.get(project_id: 99999)
rescue Basecamp::NotFoundError => e
  puts "Project not found: #{e.message}"
rescue Basecamp::RateLimitError => e
  puts "Rate limited, retry after: #{e.retry_after} seconds"
rescue Basecamp::AuthError => e
  puts "Authentication failed: #{e.message}"
rescue Basecamp::ApiError => e
  puts "API error (#{e.http_status}): #{e.message}"
end

Error Types

ErrorDescription
ApiErrorBase error class for all API errors
AuthErrorAuthentication failures (401)
ForbiddenErrorAccess denied (403)
NotFoundErrorResource not found (404)
ValidationErrorInvalid request data (400, 422)
RateLimitErrorRate limit exceeded (429)
NetworkErrorConnection failures

Observability Hooks

Monitor SDK behavior with hooks:

class MyHooks
  include Basecamp::Hooks

  def on_request_start(info)
    puts "Starting #{info.method} #{info.url}"
  end

  def on_request_end(info, result)
    puts "Completed in #{result.duration}s with status #{result.status_code}"
  end

  def on_retry(info, attempt, error, delay)
    puts "Retrying attempt #{attempt} after #{delay}s"
  end

  def on_paginate(url, page)
    puts "Fetching page #{page}"
  end
end

client = Basecamp::Client.new(
  config: config,
  token_provider: token_provider,
  hooks: MyHooks.new
)

Environment Variables

VariableDescription
BASECAMP_TOKENOAuth access token
BASECAMP_ACCOUNT_IDAccount ID
BASECAMP_BASE_URLAPI base URL (default: https://3.basecampapi.com)

Development

# Install dependencies
bundle install

# Run tests
bundle exec rake test

# Run linter
bundle exec rubocop

# Generate types from OpenAPI
ruby scripts/generate-types.rb

License

MIT