generate

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 28, 2026 License: MIT Imports: 0 Imported by: 0

README

Basecamp Go SDK

Go Reference Test Go Report Card

Official Go SDK for the Basecamp API.

Features

  • Full coverage of 30+ Basecamp API services
  • OAuth 2.0 authentication with automatic token refresh
  • Static token authentication for simple integrations
  • ETag-based HTTP caching for efficient API usage
  • Automatic retry with exponential backoff
  • Pagination handling with GetAll()
  • Structured errors with CLI-friendly exit codes
  • Secure credential storage (system keyring with file fallback)

Installation

go get github.com/basecamp/basecamp-sdk/go

Requires Go 1.25 or later.

Quick Start

Using a Static Token
package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/basecamp/basecamp-sdk/go/pkg/basecamp"
)

func main() {
    // Configure the client
    cfg := basecamp.DefaultConfig()

    // Use a static token
    token := &basecamp.StaticTokenProvider{
        Token: os.Getenv("BASECAMP_TOKEN"),
    }

    client := basecamp.NewClient(cfg, token)

    // Get account ID from environment (ForAccount validates it's numeric)
    accountID := os.Getenv("BASECAMP_ACCOUNT_ID")
    if accountID == "" {
        log.Fatal("BASECAMP_ACCOUNT_ID environment variable is required")
    }
    account := client.ForAccount(accountID)

    // List all projects
    projects, err := account.Projects().List(context.Background(), nil)
    if err != nil {
        log.Fatal(err)
    }

    for _, p := range projects {
        fmt.Printf("%d: %s\n", p.ID, p.Name)
    }
}
Using OAuth 2.0
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"

    "github.com/basecamp/basecamp-sdk/go/pkg/basecamp"
)

func main() {
    cfg := basecamp.DefaultConfig()

    // AuthManager handles token storage and refresh
    authMgr := basecamp.NewAuthManager(cfg, http.DefaultClient)
    client := basecamp.NewClient(cfg, authMgr)

    // Discover available accounts (account-agnostic operation)
    info, err := client.Authorization().GetInfo(context.Background(), nil)
    if err != nil {
        log.Fatal(err)
    }

    // Create an account-scoped client
    account := client.ForAccount(fmt.Sprint(info.Accounts[0].ID))

    // List active projects
    projects, err := account.Projects().List(context.Background(), &basecamp.ProjectListOptions{
        Status: basecamp.ProjectStatusActive,
    })
    if err != nil {
        log.Fatal(err)
    }

    for _, p := range projects {
        fmt.Printf("%s (%d)\n", p.Name, p.ID)
    }
}

Configuration

Environment Variables
Variable Description Required
BASECAMP_TOKEN Static API token or OAuth access token Yes (unless using OAuth flow)
BASECAMP_PROJECT_ID Default project ID No
BASECAMP_TODOLIST_ID Default todolist ID No
BASECAMP_BASE_URL API base URL No (default: https://3.basecampapi.com)
BASECAMP_CACHE_DIR Cache directory path No (default: ~/.cache/basecamp)
BASECAMP_CACHE_ENABLED Enable HTTP caching No (default: false)
BASECAMP_NO_KEYRING Disable system keyring No

Note: Account ID is specified via client.ForAccount(accountID) rather than configuration.

Programmatic Configuration
cfg := basecamp.DefaultConfig()
cfg.ProjectID = "67890"           // Optional default project
cfg.CacheEnabled = true           // Enable ETag caching
cfg.CacheDir = "/custom/cache"    // Custom cache location

// Or load from environment
cfg.LoadConfigFromEnv()

// Or load from JSON file
cfg, err := basecamp.LoadConfig("/path/to/config.json")

API Coverage

Projects & Organization
Service Methods
Projects() List, Get, Create, Update, Trash
Templates() List, Get, CreateProject
Tools() Get, List, Update (enable/disable/reorder dock tools)
People() List, Get, ListPingable, Me, ListProjectPeople
To-dos
Service Methods
Todos() List, Get, Create, Update, Trash, Complete, Uncomplete, Reposition
Todosets() Get
Todolists() List, Get, Create, Update, Trash
TodolistGroups() List, Get, Create, Reposition
Messages & Communication
Service Methods
Messages() List, Get, Create, Update, Trash
MessageBoards() Get
MessageTypes() List, Get, Create, Update, Destroy
Comments() List, Get, Create, Update, Trash
Campfires() List, Get, ListLines, GetLine, CreateLine, DeleteLine, Chatbot CRUD
Forwards() List, Get
Scheduling
Service Methods
Schedules() Get, ListEntries, GetEntry, CreateEntry, UpdateEntry, TrashEntry, GetEntryOccurrence, UpdateSettings
Lineup() List, Get, Create, Update, Delete
Checkins() Get, List, ListQuestions, GetQuestion, ListAnswers, GetAnswer, UpdateAnswer
Files & Documents
Service Methods
Vaults() Get, List, Create, Update
Attachments() CreateUploadURL, Create
Card Tables (Kanban)
Service Methods
CardTables() Get, ListColumns, GetColumn
Cards() List, Get, Create, Update, Move
CardColumns() List, Get, Create, Update, Watch, Unwatch
CardSteps() List, Get
Service Methods
Timeline() Progress, ProjectTimeline, PersonProgress
Reports() AssignablePeople, AssignedTodos, OverdueTodos, UpcomingSchedule
Timesheet() MyEntries, ProjectEntries
Search() Search
Events() List, ListForRecording
Integrations
Service Methods
Webhooks() List, Get, Create, Update, Delete
Subscriptions() List, Subscribe, Unsubscribe, Update
Recordings() Archive, Unarchive, Trash
Client Portal
Service Methods
ClientApprovals() Get, ListResponses, GetResponse
ClientCorrespondences() List, Get, Create, Update, Trash

Working with Todos

ctx := context.Background()

// List todos in a todolist
todos, err := account.Todos().List(ctx, todolistID, nil)

// Create a todo
todo, err := account.Todos().Create(ctx, todolistID, &basecamp.CreateTodoRequest{
    Content:     "Review pull request",
    Description: "Check the new authentication flow",
    DueOn:       "2026-02-01",
    AssigneeIDs: []int64{12345},
})

// Complete a todo
err = account.Todos().Complete(ctx, todoID)

// Reposition a todo
err = account.Todos().Reposition(ctx, todoID, 1) // Move to first position

Working with Messages

ctx := context.Background()

// Get the message board (boardID from project dock/tools)
var boardID int64 = 12345
board, err := account.MessageBoards().Get(ctx, boardID)

// List messages
messages, err := account.Messages().List(ctx, board.ID, nil)

// Create a message
msg, err := account.Messages().Create(ctx, board.ID, &basecamp.CreateMessageRequest{
    Subject: "Weekly Update",
    Content: "<p>Here's what we accomplished this week...</p>",
})

Working with Campfire

ctx := context.Background()

// List all campfires
campfires, err := account.Campfires().List(ctx)

// Send a message
line, err := account.Campfires().CreateLine(ctx, campfireID, "Hello, team!")

// List recent messages
lines, err := account.Campfires().ListLines(ctx, campfireID)

Working with Webhooks

ctx := context.Background()
var bucketID int64 = 12345 // project/bucket ID

// Create a webhook
webhook, err := account.Webhooks().Create(ctx, bucketID, &basecamp.CreateWebhookRequest{
    PayloadURL: "https://example.com/webhook",
    Types:      []string{"Todo", "Comment"},
})

// List webhooks
webhooks, err := account.Webhooks().List(ctx, bucketID)

// Delete a webhook
err = account.Webhooks().Delete(ctx, webhookID)

Error Handling

The SDK provides structured errors with codes for programmatic handling:

projects, err := account.Projects().List(ctx, nil)
if err != nil {
    if apiErr, ok := err.(*basecamp.Error); ok {
        switch apiErr.Code {
        case basecamp.CodeNotFound:
            // Handle not found
        case basecamp.CodeAuth:
            // Handle authentication error
        case basecamp.CodeRateLimit:
            // Handle rate limiting (SDK retries automatically)
        case basecamp.CodeForbidden:
            // Handle permission error
        default:
            // Handle other errors
        }

        // Errors include helpful hints
        fmt.Printf("Error: %s\nHint: %s\n", apiErr.Message, apiErr.Hint)

        // Use exit codes for CLI applications
        os.Exit(apiErr.ExitCode())
    }
}
Error Codes
Code Meaning Exit Code
usage Invalid arguments or configuration 1
not_found Resource not found 2
auth_required Authentication required 3
forbidden Access denied 4
rate_limit Rate limited (retryable) 5
network Network error (retryable) 6
api_error Server error 7
ambiguous Multiple matches found 8
validation Validation error (400, 422) 9

Caching

The SDK supports ETag-based caching for GET responses. Caching is disabled by default to avoid writing private data to disk unexpectedly.

To enable caching:

cfg := basecamp.DefaultConfig()
cfg.CacheEnabled = true

// Or via environment variable:
// BASECAMP_CACHE_ENABLED=true

When enabled, the SDK caches GET responses using ETags:

// First request fetches from API
projects, _ := account.Projects().List(ctx, nil)

// Second request uses cached data if unchanged (304 Not Modified)
projects, _ = account.Projects().List(ctx, nil)

Custom HTTP Client

httpClient := &http.Client{
    Timeout: 60 * time.Second,
    Transport: &http.Transport{
        MaxIdleConns: 50,
    },
}

client := basecamp.NewClient(cfg, token, basecamp.WithHTTPClient(httpClient))

Observability

The SDK provides a hooks interface for observability at two levels:

  • Operation-level: Semantic SDK operations like Todos.Complete, Projects.List
  • Request-level: HTTP requests including retries, caching, and timing
Debug Logging with SlogHooks

For debugging or verbose CLI modes, use SlogHooks to log all SDK activity:

import (
    "log/slog"
    "os"
    "github.com/basecamp/basecamp-sdk/go/pkg/basecamp"
)

// Create a debug logger
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))

// Enable observability hooks
hooks := basecamp.NewSlogHooks(logger)
client := basecamp.NewClient(cfg, token, basecamp.WithHooks(hooks))

Output:

level=DEBUG msg="basecamp operation start" service=Todos operation=Complete resource_type=todo is_mutation=true
level=DEBUG msg="basecamp request start" method=POST url=https://3.basecampapi.com/123/todos/789/completion.json attempt=1
level=DEBUG msg="basecamp request complete" method=POST url=... duration=145ms status=204 from_cache=false
level=DEBUG msg="basecamp operation complete" service=Todos operation=Complete duration=147ms
OpenTelemetry Integration

For distributed tracing and metrics with OTel:

import (
    "github.com/basecamp/basecamp-sdk/go/pkg/basecamp"
    basecampotel "github.com/basecamp/basecamp-sdk/go/pkg/basecamp/otel"
)

// Uses global TracerProvider/MeterProvider by default
hooks := basecampotel.NewHooks()
client := basecamp.NewClient(cfg, token, basecamp.WithHooks(hooks))

// Or with custom providers
hooks := basecampotel.NewHooks(
    basecampotel.WithTracerProvider(tp),
    basecampotel.WithMeterProvider(mp),
)

Creates spans like:

  • Todos.Complete (operation span)
    • basecamp.request (HTTP span, child of operation)
Prometheus Metrics

For Prometheus-style metrics:

import (
    "github.com/basecamp/basecamp-sdk/go/pkg/basecamp"
    basecampprom "github.com/basecamp/basecamp-sdk/go/pkg/basecamp/prometheus"
    "github.com/prometheus/client_golang/prometheus"
)

hooks := basecampprom.NewHooks(prometheus.DefaultRegisterer)
client := basecamp.NewClient(cfg, token, basecamp.WithHooks(hooks))

Exposes metrics:

Metric Type Labels
basecamp_operation_duration_seconds Histogram operation
basecamp_operations_total Counter operation, status
basecamp_http_requests_total Counter http_method, status_code
basecamp_retries_total Counter http_method
basecamp_cache_operations_total Counter result
basecamp_errors_total Counter http_method, type
Combining Multiple Backends

Use NewChainHooks to send telemetry to multiple backends:

import (
    "github.com/basecamp/basecamp-sdk/go/pkg/basecamp"
    basecampotel "github.com/basecamp/basecamp-sdk/go/pkg/basecamp/otel"
    basecampprom "github.com/basecamp/basecamp-sdk/go/pkg/basecamp/prometheus"
)

otelHooks := basecampotel.NewHooks()
promHooks := basecampprom.NewHooks(prometheus.DefaultRegisterer)

client := basecamp.NewClient(cfg, token,
    basecamp.WithHooks(basecamp.NewChainHooks(otelHooks, promHooks)),
)
Custom Hooks

Implement the Hooks interface for custom behavior. Embed NoopHooks to only override what you need:

type AlertingHooks struct {
    basecamp.NoopHooks
}

func (h *AlertingHooks) OnRetry(ctx context.Context, info basecamp.RequestInfo, attempt int, err error) {
    if attempt >= 3 {
        alertOncall(fmt.Sprintf("Basecamp API struggling: %s %s attempt %d", info.Method, info.URL, attempt))
    }
}

hooks := &AlertingHooks{}
client := basecamp.NewClient(cfg, token, basecamp.WithHooks(hooks))
Zero Overhead When Disabled

By default, the SDK uses NoopHooks which compiles to nothing—no overhead when observability isn't needed.

Logging

Enable HTTP-level debug logging with a custom slog logger:

logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))

client := basecamp.NewClient(cfg, token, basecamp.WithLogger(logger))

For semantic operation logging (recommended), use SlogHooks instead—see Observability above.

License

MIT

Documentation

Overview

Package generate provides code generation directives for the Basecamp SDK.

Run `go generate ./...` from the go directory to regenerate the client code.

Directories

Path Synopsis
pkg
basecamp
Package basecamp provides a Go SDK for the Basecamp API.
Package basecamp provides a Go SDK for the Basecamp API.
basecamp/oauth
Package oauth provides OAuth 2.0 discovery, token exchange, and refresh functionality.
Package oauth provides OAuth 2.0 discovery, token exchange, and refresh functionality.
basecamp/otel
Package otel provides OpenTelemetry integration for the Basecamp SDK.
Package otel provides OpenTelemetry integration for the Basecamp SDK.
basecamp/prometheus
Package prometheus provides Prometheus metrics integration for the Basecamp SDK.
Package prometheus provides Prometheus metrics integration for the Basecamp SDK.
generated
Package generated provides primitives to interact with the Basecamp HTTP API.
Package generated provides primitives to interact with the Basecamp HTTP API.
types
Package types provides shared types used across the Basecamp SDK.
Package types provides shared types used across the Basecamp SDK.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL